本文参考:
https://www.jianshu.com/p/7a2d2d7497a1(主要参考)
https://stackoverflow.com/questions/22450036/refreshing-oauth-token-using-retrofit-without-modifying-all-calls
1.问题出现场景
我们知道,现在身份验证中一般会有两个Token:
AccessToken
和RefreshToken
在每次请求中,我们需要把AccessToken加入到header中来进行请求,否则会因为身份认证不通过,导致401错误,请求失败。
但是AccessToken是会过期的,通常过期时间在12小时或者一天等待(不同企业不同定制),但是RefreshToken过期时间是一个月甚至更久。
所以在AccessToken过期后,我们需要使用RefreshToken来进行刷新AccessToken
2.两个实现思路
方式一.主动刷新:
AccessToken是JWT数据,可以解析过期时间,甚至有些登陆接口会直接返回过期时间,所以在每次将AccessToken加入到Header之前,判断AccessToken是否过期,如果过期,主动进行刷新
在线解析JWT数据:http://jwt.calebb.net/
方式二.全局拦截401错误,被动刷新(推荐):
在每次请求中设置拦截器,拦截到401错误时,进行AccessToken刷新,刷新成功后,将新的AccessToken加入到Header,再次发出请求
这种方式对于用户来说完全无感,且不会出现疏漏 推荐
3.使用Interceptor
来实现静默刷新(方式二)
注:官方推荐使用Authorization来进行,这里不使用的原因请看参考文章一
首先我们完成拦截器代码
新建ClassAuthenticatorInterceptor
实现接口Interceptor
以下是 AuthenticatorInterceptor.class
拦截器代码
class AuthenticatorInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// 拦截获取请求
val request = chain.request()
// 获取响应
val response = chain.proceed(request)
// 拦截401错误进行处理
if (response.code() == 401) {
// 这里应该调用自己的刷新token的接口
// 这里发起的请求是同步的,刷新完成token后再增加到header中
// 这里抛出的错误会直接回调 之前接口的onError方法
//updateToken()就是我进行刷新accessToken的方法,会返回新的accessToken
val newApiToken = updateToken()
// 创建新的请求,并增加header
val retryRequest = chain.request()
.newBuilder()
.header("Authorization", newApiToken)
.build()
// 再次发起请求
return chain.proceed(retryRequest)
}
//如果没有发生401错误则不进行操作,直接返回原本的请求
return response
}
//刷新accessToken方法
//注解 @Synchronized 来保证同一时间只能由一个线程来调用此方法
@Synchronized fun updateToken(): String {
var newApiToken = ""
var refushToken = ""//从数据中拿到refushToken
//调用updateToken的服务接口,传入refushToken来刷新
//execute()为同步请求 这里必须使用同步请求
//这里只使用到了retrofit没有使用rxjava
//固定值
//refresh_token="refresh_token"
//grant_type="openid"
var data = service.updateToken(refushToken, "refresh_token", "openid")
.execute()
//根据请求得到的数据data的code判断同步请求是否成功
//200就是成功了
if (data.code()==200){
//获取到请求返回数据体
var t=data.body()
//t是请求接口的实体类对象
if (t!=null){
//这里获取到新的accessToken
val actualToken = t.tokenType + " " + t.accessToken
newApiToken = actualToken
//同时Token可以进行保存操作
//...
}
//返回新的accessToken
return newApiToken
}
return newApiToken
}
}
以上就是拦截器的代码了,总结一下我们在这个拦截器中,拦截到了401错误,然后刷新Token,使用新的accessToken再次发出请求
那么我们怎么将拦截器使用起来呢?请看:
在项目中统一创建retrofit对象的地方加入拦截器
加入拦截器代码:
fun initClint(): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.addInterceptor(AuthenticatorInterceptor())
//此处还可以builder.addInterceptor()添加其他拦截器 比如log打印日志什么的
return builder.build()
}
Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.client(initClint())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build()
现在就实现了请求401错误拦截并处理
4.补充一点小说明
这是刷新accessToken的api接口
@FormUrlEncoded
@Headers(
"Accept:application/json",
"Content-Type:application/x-www-form-urlencoded",
"charset:UTF-8"
)
@POST("你的接口")
fun updateToken(
@Header("Authorization") apiToken:String,
@Field("refresh_token") freshToken: String,
@Field("grant_type") refreshToken: String,
@Field("scope") scope: String
): Call
这是service单独创建retrofit对象的方法:
fun updateToken(refresh_token:String,grantType: String, scope: String): Call {
return Retrofit
.Builder()
.baseUrl(Constants.BASE_URL)
.client(initClint())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(CommonApi::class.java).updateToken("固定授权码",refresh_token,grantType,scope)
}
这里我们使用了Call