跳至主要內容

Token失效问题排查

pptg大约 2 分钟

在过去的一年里,该问题大概出现过3-4次?由于触发概率极低故一直没有找到问题的根源所在

今天下午刚好又触发了这个问题,故集中排查了前后端相关代码,最后定位到了问题所在

1. 问题描述

平台偶发Token失效问题,具体表现为:

  • 偶发执行某些请求后,报错401Token失效
  • 随后进行登陆,每次登陆成功后均被立即踢出,报错401Token失效

2. 登陆、刷新Token相关流程

目前前后端的登陆、刷新Token流程如下:

前后端相关流程
前后端相关流程
  1. 登陆
    • 在登陆情况下,前端发送用户信息至后端
    • 后端校验后将Token保存至服务器并返回给前端
    • 前端收到后存到浏览器缓存
  2. Token失效时
    • 前端发送的请求到达后端,后端判断当前Token失效,返回401给前端
    • 前端收到401,清空缓存Token并跳转至登陆页,让用户重新登陆
  3. 定时刷新Token
    • 前端定时触发刷新Token的请求
    • 后端收到该请求后,清空服务器中原的Token并产生新的Token给前端 (问题就出在这里)

3. 问题原因

Token刷新时序
Token刷新时序

正是因为后端在刷新的时候先将Token删除了再产生新的Token,这个过程中会导致有非常短暂的时间该用户是没有合法的Token可用的。具体表现为从服务端删除Token的时刻起到前端接收到请求响应的新Token止,这段时间内前端的一切请求都是401Token不合法的。

此时前端表现为收到401,并跳转至登陆页,由于该过程很快,所以后端下发的Token会在跳转登陆页之后再传输至前端,并交给定时器存在缓存里

之后即使用户再次登陆,由于后端的登陆逻辑会先判断是否已经登陆,所以后端会继续清空Token的信息

4. 解决方案

  1. 前端对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')
  }
},
  1. 后端对Token的废弃做适当的延长, 用redis的expire取代del,比如设置5秒的过期时间(设置成大于单次网络传播时延,确保新Token抵达前端之前,旧Token仍可生效)