Android知识

retrofit 刷新token并发处理

2017-07-01  本文已影响4035人  ZHG

背景说明

在app开发中,我们需要保证用户登录之后,如果没有在其他设备上登录,则不需要再次登录,很多都会使用 token 作为安全令牌,开始阶段都会在登录时候获取,一直使用到下次登录。这样的 token 没有什么安全性可言,所以大多app都会做 token 时效性处理。
刷新 token 流程图如下:


刷新 token.png

根据刷新 token 的流程,后台返回以下信息:

rest Code errorCode token RefreshToken 刷新Token 重新登录获取Token
401 4010 token为空 无效
401 4011 token过期 有效
401 4012 token失效 失效
401 4013 token过期 过期
401 4014 token失效 失效
rest Code errorCode 问题说明 解决办法
400 4001 请求参数为空 需要检查逻辑

Android 端对 token 的处理是,在每次用到 token 的时候对 token 是否有效进行判断,获得有效的 token。具体代码如下:

public static Retrofit retrofitClient(String token, String apiUrl) {
        OkHttpClient client = new OkHttpClient.Builder()
                .readTimeout(30, TimeUnit.SECONDS)
                .connectTimeout(60, TimeUnit.SECONDS).
                        addInterceptor(new Interceptor() {
                            @Override
                            public okhttp3.Response intercept(Chain chain) throws IOException {
                                Request original = chain.request();
                                Request request = original.newBuilder()
                                  .addHeader(AUTH_HEADER_KEY
                                      , BEARER_HEADER_VALUE
                                    + getToken())
                                  .method(original.method(), original.body())
                                  .build();
                                return chain.proceed(request);
                            }
                        }).build();
        return new Retrofit.Builder()
                .baseUrl(apiUrl)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
public static String getToken(){
        final String token = "";
        TokenEntity entity = UserUtil.getInstance().getToken();
         long startTime = getStartTime();//获取token时的时间,单位:s
        long currentTime = System.currentTimeMillis()/1000;//获取当前时间
         //判断token的有效期是否在时间段内
        if ((currentTime - startTime) < Long.parseLong(entity.getExpiresIn())){
            token = entity.getToken();
        }else {
            //请求刷新token接口,获取新的token
        }
        return token;
    }

然而当 token 失效时,出现了多个接口同时 token 失效,并都用 refreshToken 进行 token 刷新,出现接口调用错误,具体情况如下。

问题说明

问题解决思路

在 retrofit 中有同步请求 execute() 和异步请求 enqueue(XXX) ,个人还没有找到可以实现多个接口并发,而且同步操作的方法。网上有人说建议使用 retrofit+Rxjava 可以实现,但是本人对 Rx 才上手还没有深入了解。所以能否解决这个问题,我还不知道。我解决问题的思路如下:

解决办法

多线程同步可以解决当下的问题,同步有三种方法:

public synchronized String getToken(){
        final String token = "";
        //对 token 进行有效判断,有效则使用原有的;无效则对 token 进行刷新操作
        // ⚠️刷新 token 的接口要使用同步操作,否则无用
        //....
        return token;
    }
synchronized (this){
        //需要同步的代码块    
  }
private Lock mLock = new ReentrantLock();
    public String getToken(){
        mLock.lock();
        try{
               //需要上锁的代码块
        }finally{
          mLock.unlock();
        }
    }

此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

我使用的第一种解决办法,因为整个获取 token 的方法都需要同步实现, synchronized 关键字可以同步一个方法,也可以同步代码块(方法二),使用起来方便简洁,虽然没有 ReenreantLock 使用灵活,但是对于这个问题,使用 synchronized 已经足够了,代码如下:

public synchronized String getToken(){
        final String token = "";
        TokenEntity entity = UserUtil.getInstance().getToken();//获取登录时返回的 token 实体
         long startTime = getStartTime();//获取 token 时的时间,单位:s
        long currentTime = System.currentTimeMillis()/1000;//获取当前时间
         //判断 token 的有效期是否在时间段内
        if ((currentTime - startTime) < Long.parseLong(entity.getExpiresIn())) {
            token = entity.getToken();
        } else {
            //根据 refreshToken 刷新 token,获得最新 token
            //⚠️需要使用同步请求
        }
        return token];
    }

涉及知识点

总结

在解决这个问题上,花费了我三天时间,想到延迟、消息队列、线程队列、同步请求等,经过所有的尝试后仍然解决不了问题,经过和同事的讨论,他们提供给我多线程同步的思路,经过查找资料,在获取 token 的方法上添加一个 synchronized 关键字,并使用 retrofit 的同步方法请求接口,问题得到解决。只有掌握更多的知识,才能够找到更好的思路。

参考文档
http://www.codeceo.com/article/java-multi-thread-sync.html 描述多线程同步的五种方法,并进行粗略的对比
https://stackoverflow.com/questions/31021725/android-okhttp-refresh-expired-token 讲述retrofit 进行网络请求,token 过期之后发生的并发问题,并进行解决方法的讨论。
http://blog.csdn.net/jdsjlzx/article/details/52442113 使用RxJava+retrofit进行网络请求,解决 token 失效,并刷新 token 的方法。

上一篇 下一篇

猜你喜欢

热点阅读