当前位置: 移动技术网 > IT编程>开发语言>.net > 移动端双Token免登录(附代码)

移动端双Token免登录(附代码)

2020年07月29日  | 移动技术网IT编程  | 我要评论

参考自:https://blog.csdn.net/huweijian5/article/details/88903561

前奏

  1. 在安卓中一开始使用一个Token进行接口安全,但是Token假如过期时间设置的长,难免会有安全风险,假如设置的时间端,就会出现用户没用多久,就会使得用户需要重新登录
  2. 采用双Token的方式,来使用户无感知的刷新Token,实现真正的免登录

设计

  1. 用户在登录之后返回access_token和refresh_token(这里假定他们的有效期分别是2小时和7天)
  2. 当access_token未过期时,则请求正常
  3. 当access_token过期了,此时服务端会返回过期提示给到客户端,客户端收到过期提示后,使用refresh_token去获取新的access_token和refresh_token(此时他们的有效期就又变为2小时和7天,旧的自然失效)
  4. 当refresh_token也过期了,使用它去获取新access_token时服务端就会返回过期提示,那么此时就应该让用户重新登录了
  5. 每次使用access_token时,都会更新refresh_token的有效期
  6. 流程图如下:
    流程图
  7. 以上设计的一个好处就是只要用户在7天内有操作,那么就可以不用重新登录,而如果用户在7天内没有任何操作,那么则需要用户重新登录
  8. 使用长短周期token的好处就是短周期token可以防止被截获后无休止使用,长周期token又可以保证用户不用一直登录,毕竟登录用的是账号和密码,这种数据在网络传输中越少越好。
  9. 之所以在使用refresh_token时都返回新的refresh_token,而不是延长旧的refresh_token的有效期,主要是为了安全,避免refresh_token被非法截获后一直可用
  10. 假如每次都生成新的refresh_token,那么即使旧的refresh_token被截获,在用户合法刷新后就会生成新的refresh_token,导致被截获的那个旧refresh_token失效,从而提升安全性
  11. 代码如下,注释很详细:
/**
 * @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
    }
}

本文地址:https://blog.csdn.net/llayjun/article/details/107628265

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网