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
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!