app的token續期---雙token和前端處理


視頻地址
https://www.bilibili.com/video/BV1FE411M7pk?p=34

其他相關文檔

https://blog.csdn.net/a704397849/article/details/90216739

https://segmentfault.com/a/1190000041045953

雙token,帶比喻
https://www.zhihu.com/question/316165120?sort=created

另一篇

前奏
在安卓中一開始使用一個Token進行接口安全,但是Token假如過期時間設置的長,難免會有安全風險,假如設置的時間端,就會出現用戶沒用多久,就會使得用戶需要重新登錄
采用雙Token的方式,來使用戶無感知的刷新Token,實現真正的免登錄
設計
用戶在登錄之后返回access_token和refresh_token(這里假定他們的有效期分別是2小時和7天)
當access_token未過期時,則請求正常
當access_token過期了,此時服務端會返回過期提示給到客戶端,客戶端收到過期提示后,使用refresh_token去獲取新的access_token和refresh_token(此時他們的有效期就又變為2小時和7天,舊的自然失效)
當refresh_token也過期了,使用它去獲取新access_token時服務端就會返回過期提示,那么此時就應該讓用戶重新登錄了
每次使用access_token時,都會更新refresh_token的有效期
流程圖如下:

以上設計的一個好處就是只要用戶在7天內有操作,那么就可以不用重新登錄,而如果用戶在7天內沒有任何操作,那么則需要用戶重新登錄
使用長短周期token的好處就是短周期token可以防止被截獲后無休止使用,長周期token又可以保證用戶不用一直登錄,畢竟登錄用的是賬號和密碼,這種數據在網絡傳輸中越少越好。
之所以在使用refresh_token時都返回新的refresh_token,而不是延長舊的refresh_token的有效期,主要是為了安全,避免refresh_token被非法截獲后一直可用
假如每次都生成新的refresh_token,那么即使舊的refresh_token被截獲,在用戶合法刷新后就會生成新的refresh_token,導致被截獲的那個舊refresh_token失效,從而提升安全性
代碼如下,注釋很詳細:
/**
 * @author zyl
 * @date 2020/7/24 3:54 PM
 */
internal class TokenInterceptor : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request().newBuilder()
        // 添加默認的Token請求頭
        request.addHeader("Authorization", TokenMmkv.accessToken.toString())
        val response = chain.proceed(request.build())
        if (request.build().url.toString().contains("tokenRefresh")) {
            // 直接返回response,忽略攔截器,否則會無限攔截
            return response
        }
        val mediaType = response.body?.contentType()
        val content = response.body?.string()
        if (isTokenExpired(content ?: "")) {
            val newToken = getNewToken()
            when (newToken?.code) {
                0 -> {
                    LogUtils.d("獲取新的token和refreshToken")
                    TokenMmkv.accessToken = newToken.data.token
                    TokenMmkv.refreshToken = newToken.data.refreshToken
                }
                300 -> {
                    LogUtils.d("RefreshToken過期了,跳到登錄界面")
                    MMKV.defaultMMKV().remove("PersonId")
                    MMKV.defaultMMKV().remove("EnterpriseId")
                    MMKV.defaultMMKV().remove("isApprove")
                    // 移除之前保存的token值
                    TokenMmkv.removeToken()
                    // refreshToken 過期,跳到登錄界面
                    ARouter.getInstance().build("/person/main/activity").navigation()
                    // 這邊必須制造一個假的返回值,否值會有問題,假如直接返回response,則會報錯
                    val baseBean = BaseBean(RefreshToken("", ""), 300, "登錄過期,請重新登錄!")
                    return response.newBuilder().body(ResponseBody.create("application/json".toMediaType(), GsonUtils.toJson(baseBean))).build()
                }
            }
            // 如果token過期 再去重新請求token 然后設置token的請求頭 重新發起請求 用戶無感
            // 使用新的Token,創建新的請求
            val newRequest = chain.request()
                .newBuilder()
                .addHeader("Authorization", TokenMmkv.accessToken.toString())
                .build()
            return chain.proceed(newRequest)
        }
        return response
            .newBuilder()
            .body(ResponseBody.create(mediaType, content ?: ""))
            .build()
    }

    // 通過一個特定的接口獲取新的token,此處要用到同步的retrofit請求,這邊需要過濾掉Token攔截,防止無限循環的被攔截
    private fun getNewToken(): BaseBean<RefreshToken>? {
        // 通過一個特定的接口獲取新的token,此處要用到同步的retrofit請求
        // 要用retrofit的同步方式
        val call = Api.apiInstance.refreshToken(RequestTokenBody(TokenMmkv.refreshToken))
        var newToken: BaseBean<RefreshToken>? = null
        return try {
            newToken = call.execute().body()
            newToken
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }
    }

    /**
     * 根據Response,判斷Token
     * @return
     */
    private fun isTokenExpired(resultStr: String): Boolean {
        val (_, code) = GsonUtils.fromJson(resultStr, BaseBean::class.java)
        if (code == 300) {
            LogUtils.d("Token過期了,用RefreshToken獲取新token")
            return true
        }
        return false
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM