Android对于有时间戳和token验证的网络请求的处理

2019-1-4 liuyingcong 安卓开发

有些项目为了提高安全性,设计接口时增加了时间戳和token效验,如下所示:

获取用户信息接口.jpg

1、时间戳

上表中的参数时间戳必须与服务器当前时间对应,前后不能超过10秒,超过则请求失败:

时间戳超时返回.jpg

你肯定想问,如果手机系统时间不正确,比服务器快了或慢了10秒以上,那不每次都会请求失败吗?别着急,这样的接口设计,肯定会有一个获取服务器时间戳的接口:

时间戳接口 .jpg

思路是这样的,每次APP启动的时候获取一次服务器时间戳,然后与手机本地时间相减,计算出差额,并保存;当其他的网络请求需要传入时间戳参数时,用保存的时间差额加上手机当前时间,就可以得到正确的时间戳了:

时间差 = 获取的服务器时间戳 - 手机当前时间

时间戳 = 手机当前时间 + 时间差


当然,现在的手机大部分都是联网获取时间,很少会出现比标准时间差10秒以上的情况,服务器的时间也是标准时间,因此,只传入手机的当前时间也能符合90%的情况。

那剩下的情况是什么呢?

    1、网速慢的时候,请求时间和读取时间会比较长,很可能会超过10秒;

    2、出国用户或国外用户,不在一个时区;

    3、用户主动或被动调整了手机系统时间。

这些情况下,如果只传入手机的当前时间,是无法请求到正确的数据的,所以还是要计算时间差。


至于每次APP启动的时候获取一次服务器时间戳,这个时机对不对呢?

用户在使用App的过程中,很少会出现系统时间的调整,如果恰巧赶上,可以在接收到时间戳超期的错误码后,提示用户退出APP重新启动一次。当然也可以注册时间调整的广播接收者,接收到时间调整后,重新计算一次时间差。

至于网速慢的情况还是不能解决,加上提示用户重启APP和注册广播接收者这些不太优雅的操作,我们要寻找其他的思路。


怎么能彻底并且优雅的解决时间戳超期的问题呢?

其实我们不必每次APP启动都请求一次服务器时间戳,只有收到时间戳超期的错误之后,才有必要获取,此时计算出时间差,并保存,以后所有的请求都可以用这个时间差了,直到下一次再收到时间戳超期的错误,再获取一次即可。至于这个错误的请求,可以在计算出时间差之后,再重新请求一次。

跟着上述的思路,我们自然而然想到在请求回调基类里筛选这个时间戳超期错误,筛选出这个错误后,同步请求一次服务器时间戳,再进行一次之前的请求。想想就很困难,因为要保证在子线程里完成,还要记住之前的请求是什么,这条路走不通。


不卖关子了,okhttp的拦截器可以解决以上问题:


            Interceptor interceptor = new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request();
                    Response response= chain.proceed(request);
    
                    ResponseBody body = response.body();
    
                    BufferedSource source = body.source();
                    source.request(Long.MAX_VALUE);
                    Buffer buffer = source.buffer();
    
                    Charset charset = Charset.forName("UTF-8");
                    String json = buffer.clone().readString(charset);
    
                    BaseBean baseBean = gson.fromJson(json, BaseBean.class);
                    String code = baseBean.getCode();
    
                    if (code.equals("0210")) {// 时间戳超期
                        // 同步请求时间戳
                        Request timeRequest = new Request.Builder()
                                .url("http://uc.hivoice.cn:80/timestamp.jsp")
                                .build();
                        Response timeResponse = chain.proceed(timeRequest);
                        int timeCode = timeResponse.code();
                        if (timeCode == 200) {
                            // 计算时间差
                            String timestamp = timeResponse.body().string().trim();
                            long dTime = System.currentTimeMillis() / 1000 - Long.parseLong(timestamp);
                            SpUtil.setDTime(dTime);
            
                            // 构建新的请求
                            FormBody.Builder builder = new FormBody.Builder();
                            FormBody formBody = (FormBody) request.body();
            
                            List<String> param = new ArrayList<>();
                            for (int i = 0; i < formBody.size(); i++) {
                                String name = formBody.encodedName(i);
                                if (name.equals("timestamp")) {
                                    builder.add("timestamp", timestamp);
                                    param.add(timestamp);
                                } else if (!name.equals("signature")) {
                                    String value = formBody.encodedValue(i);
                                    param.add(value);
                                    builder.add(name, value);
                                }
                            }
            
                            String signature = SignUtil.getSignature(param);
                            builder.add("signature", signature);
            
                            Request newRequest = request.newBuilder().method("POST", builder.build()).build();
                            response = chain.proceed(newRequest);
                        }
                    }
                    return response;
                }
            };
    
            
            OkHttpClient httpClient = new OkHttpClient.Builder()
                    .addInterceptor(interceptor)
                    .build();
上述是针对post请求,考虑到这类请求多少post请求就没有区分,实际项目中如果有get请求也会发生时间戳超时问题,必须要做区分。



2、token

token也是这样,往往是APP启动的时候获取一次,保存下来,后面的请求传入这个参数即可。

但是有些情况下,会错过这个获取时机:

    1、APP启动时手机没有联网,启动以后才联网;

    2、用户启动APP以后一直没有退出,直到token过期。

对于第一种情况,有人说可以注册广播接收者,监测手机的网络状态,如果启动的时候没有网络则用户联网以后再获取token,这个思路是对的,但是有一种情况是监测不到网络变化的,即用户用安全管家之类的软件禁了APP的网络的情况。

当然,可以提示用户退出APP,重新进入。但是前面说过,这样做不太优雅,而且主流APP也没有发现过这种情况。

综上所述,token验证失败的处理也应该用okhttp的拦截器处理,思路和步骤与时间戳一致,不再重复。


3、关于性能

有人可能会担心性能问题,肯定会有影响,但是影响极其有限,因为官方出品的日志拦截器也是这么做的,看不出有什么影响。



标签: 时间戳超期 token效验失败

网站备案号:京ICP备11043289号-1 北京市公安局网络备案 海1101084571
版权所有 北京育灵童科技发展有限公司 Copyright © 2002-2024 www.elight.cn, All Rights Reserved