情况说明
在app开发中,我们需要保证用户登录之后,如果没有在其他设备上登录,则不需要再次登录,很多都会使用token作为安全令牌,开始阶段都会在登录时候获取,一直使用到下次登录。这样的token没有什么安全性可言,所以大多app都会做token时效性处理。
刷新token流程图如下:
刷新token流程图.png
暂时我了解的方案有两种:
在获取token的时候,同时获得token的有效时间和刷新token时用的refreshToken(只允许使用一次),在请求接口的时候判断token是否在有效期内,如若在这个时间内,则继续使用旧token,不在这个时间段则根据token和RefreshToken请求token刷新接口,重新获取;并且后台也会判断token是否有效,token无效,refreshToken有效,给出错误码,可以刷新token;token无效,refreshToken无效,给出错误码,重新登录获取token。
if ((currentTime - startTime) < validTime) {
//使用原有的token
} else {
//根据refreshToken刷新token,获取新的token
}
登陆获取token,使用该token一直请求接口,后台判断token是否失效,如果失效抛出401错误,客户端在请求头中截取请求Code,如果是401错误,则刷新token,并用刷新获得的新token请求接口。
public okhttp3.Response intercept(Chain chain) throws IOException {
request = request.newBuilder()
.header(AUTH_HEADER_KEY, BEARER_HEADER_VALUE + token)
.build();
Response response = chain.proceed(request);
//判断请求接口是否发生401错误
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
//同步刷新token,获得newToken
Request newRequest = request.newBuilder()
.removeHeader(AUTH_HEADER_KEY)
.addHeader(AUTH_HEADER_KEY, BEARER_HEADER_VALUE + newToken.getAccessToken())
.build();
response = chain.proceed(newRequest);
}
return response;
}
我们使用的是第一种方案,但是在开发过程中出现刷新token并发的情况
遇问题说明
在token失效期间,多个接口同时请求刷新token这个接口,由于refreshToken的有效性只为一次,当第一个请求接口去刷新token,当接口还没有请求成功,token还没有刷新,后面的接口请求时,token也是失效的,需要刷新。后面的其他接口在使用原来的refreshToken刷新,则会失败,接口调用错误,退回到登录页面,与当初设想的效果不符。
对于第二种方法,没有实践过。当多个接口的token失效,一同去刷新token,后台返回来的是同一个token还是不相同,安全性可靠么,并发是怎样处理的?希望广大网友有遇到这样的情况在下方留言,在下感激不尽。
问题解决思路
在retrofit中个人还没有找到可以实现多个接口并发,而且同步操作的方法。往上有人说建议使用retrofit+Rxjava可以实现,但是本人对Rx才上手还没有深入了解。所以能否解决这个问题,我还不知道。我解决问题的思路如下:
思路一
接口几乎是同时请求,那么我在请求每个接口的时候都增加一个Thread.sleep(1000);当网络比较慢的时候效果还可以,但是当网络状态良好,问题相当于没有解决。
思路二
使用handler.sendMessage();使用消息队列进行处理,问题仍然没有解决
思路三
多线程同步,每次请求都是一个异步操作,想让获取token这个过程按照顺序来,就需要同步操作或者线程队列。这个思路解决所遇到的问题。
多线程同步
解决当下的问题,有三种方法:
一是使用synchronized关键字,对方法进行同步。
public synchronized String getToken(){
final String token = "";
//对token进行有效判断,有效则使用原有的;无效则对token进行刷新操作
// :warning:刷新token的接口要使用同步操作,否则无用
//....
return token;
}
二是使用synchronized(object){}同步代码快的方法,把需要同步的操作放到“{}”内。与一是一样的
synchronized (this){
//需要同步的代码块
}
三是使用重入锁ReenreantLock实现线程同步
private Lock mLock = new ReentrantLock();
public String getToken(){
mLock.lock();
try{
//需要上锁的代码块
}finally{
mLock.unlock();
}
}
此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
涉及知识点
synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。
ReentrantLock:官方说明是一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
:warning:多线程同步操作是一种耗费资源,耗时的操作,在开发中能不使用尽量不使用。
总结
解决刷新token这个问题上,需要我们了解retrofit的同步异步请求,同时需要知道如何在retrofit的请求中添加header,并用多线程同步的知识进行token的获取,至少需要了解synchronized或ReentrantLock 其中一种的用法和实现。只有掌握更多的知识,才可以解决更加艰难的问题。
参考文档
注意:本文来自简书。本站无法对本文内容的真实性、完整性、及时性、原创性提供任何保证,请您自行验证核实并承担相关的风险与后果!
CoLaBug.com遵循[CC BY-SA 4.0]分享并保持客观立场,本站不承担此类作品侵权行为的直接责任及连带责任。您有版权、意见、投诉等问题,请通过[eMail]联系我们处理,如需商业授权请联系原作者/原网站。