本文參考:
http://www.lxweimin.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 = ""http://從數據中拿到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<LoginBean>
這是service單獨創建retrofit對象的方法:
fun updateToken(refresh_token:String,grantType: String, scope: String): Call<LoginBean> {
return Retrofit
.Builder()
.baseUrl(Constants.BASE_URL)
.client(initClint())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(CommonApi::class.java).updateToken("固定授權碼",refresh_token,grantType,scope)
}
這里我們使用了Call<T>來發出請求,而不是Observable<T>,是為了調同call..execute()發出同步請求,所以對于刷新Token的接口不需要使用到rxjava