Token的靜默刷新(AccessToken過期后使用RefreshToken進行刷新)

本文參考:
http://www.lxweimin.com/p/7a2d2d7497a1(主要參考)
https://stackoverflow.com/questions/22450036/refreshing-oauth-token-using-retrofit-without-modifying-all-calls

1.問題出現場景

我們知道,現在身份驗證中一般會有兩個Token:
AccessTokenRefreshToken
在每次請求中,我們需要把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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。