Token失效问题排查
大约 2 分钟
在过去的一年里,该问题大概出现过3-4次?由于触发概率极低故一直没有找到问题的根源所在
今天下午刚好又触发了这个问题,故集中排查了前后端相关代码,最后定位到了问题所在
1. 问题描述
平台偶发Token失效问题,具体表现为:
- 偶发执行某些请求后,报错401Token失效
- 随后进行登陆,每次登陆成功后均被立即踢出,报错401Token失效
2. 登陆、刷新Token相关流程
目前前后端的登陆、刷新Token流程如下:
- 登陆
- 在登陆情况下,前端发送用户信息至后端
- 后端校验后将Token保存至服务器并返回给前端
- 前端收到后存到浏览器缓存
- Token失效时
- 前端发送的请求到达后端,后端判断当前Token失效,返回401给前端
- 前端收到401,清空缓存Token并跳转至登陆页,让用户重新登陆
- 定时刷新Token
- 前端定时触发刷新Token的请求
- 后端收到该请求后,清空服务器中原的Token并产生新的Token给前端 (问题就出在这里)
3. 问题原因
正是因为后端在刷新的时候先将Token删除了再产生新的Token,这个过程中会导致有非常短暂的时间该用户是没有合法的Token可用的。具体表现为从服务端删除Token的时刻起到前端接收到请求响应的新Token止,这段时间内前端的一切请求都是401Token不合法的。
此时前端表现为收到401,并跳转至登陆页,由于该过程很快,所以后端下发的Token会在跳转登陆页之后再传输至前端,并交给定时器存在缓存里
之后即使用户再次登陆,由于后端的登陆逻辑会先判断是否已经登陆,所以后端会继续清空Token的信息
4. 解决方案
- 前端对Token的出口做统一限流,根据时间进行判断
refreshToken() {
const key = "lastRefreshTime"
const lastRefreshTime = localStorage.getItem(key)
const currentTime = Date.now()
if (!lastRefreshTime || (currentTime - parseInt(lastRefreshTime)) >= 30*1000) {
localStorage.setItem(key, currentTime.toString());
return request.get('/auth/tenant/refreshToken')
}
},
- 后端对Token的废弃做适当的延长, 用
redis的expire
取代del
,比如设置5秒的过期时间(设置成大于单次网络传播时延,确保新Token抵达前端之前,旧Token仍可生效)