1. Token无感刷新机制概述
作为一名前端开发者,我经常遇到这样的场景:用户正在填写一个复杂的表单,突然系统提示"登录已过期",所有输入的数据瞬间消失。这种体验简直让人抓狂!而Token无感刷新正是为了解决这个痛点而生的技术方案。
简单来说,Token无感刷新就是在用户毫无察觉的情况下,自动更新过期的认证令牌(Token),保持用户会话持续有效。这就像是在电影院看电影时,工作人员悄悄更换了放映机的胶片,观众完全感受不到中断。
在实际项目中,我使用Vue.js配合axios拦截器实现了这套机制,效果非常理想。用户可以在不被打断的情况下持续工作数小时,即使背后的Token已经刷新了多次。下面我就详细分享这套方案的设计思路和具体实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么需要Token无感刷新?
2.1 传统Token方案的痛点
在传统的单Token方案中,我们通常会遇到以下几个问题:
-
频繁的中断体验:当Token过期时,用户会被强制退出当前操作,需要重新登录。根据我的实测数据,在Token有效期设为2小时的情况下,活跃用户平均每天会遇到3-5次强制退出。
-
数据丢失风险:特别是对于表单填写这类场景,用户可能已经输入了大量数据,突然的退出会导致所有未保存的内容丢失。我在电商后台系统中就遇到过用户投诉,说他们花了半小时填写的商品信息突然消失。
-
操作流程断裂:用户需要记住中断时的操作节点,重新登录后继续之前的流程。这种上下文切换会显著降低工作效率,增加出错概率。
2.2 双Token机制的优势
为了解决上述问题,现代认证系统普遍采用双Token机制:
-
Access Token:短期有效的访问令牌,通常有效期在15分钟到2小时之间。它就像一张临时通行证,每次API请求都需要携带。
-
Refresh Token:长期有效的刷新令牌,通常有效期在7天到1个月。它相当于一张VIP卡,专门用来获取新的Access Token。
这种设计有几个关键优势:
- 安全性:即使Access Token被盗,由于其有效期短,危害有限
- 连续性:Refresh Token可以保证用户会话长期有效
- 灵活性:可以根据业务需求调整两种Token的有效期
3. 核心实现原理详解
3.1 双Token的工作机制
让我们用一个现实生活中的类比来理解双Token机制:
想象你进入一个高度安全的办公大楼。Access Token就像你的当日访客卡(有效期一天),而Refresh Token则是你的长期员工卡(有效期一年)。每天进门时,你可以用员工卡换取新的访客卡。如果访客卡过期了,保安会让你用员工卡换一张新的,而不是把你赶出大楼。
在技术实现上,这套机制包含以下几个关键点:
-
Token的存储策略:
- Access Token:存储在内存或短期存储中
- Refresh Token:存储在HttpOnly的Cookie或安全存储中
-
刷新时机选择:
- 主动刷新:在Access Token即将过期时提前刷新
- 被动刷新:当API返回401错误时触发刷新
-
并发请求处理:
- 避免多个并发请求同时触发刷新
- 将等待中的请求加入队列,刷新后重试
3.2 请求拦截与重试流程
这是整个机制最核心的部分,我将其实现流程总结如下:
- 请求拦截:在axios请求拦截器中检查Access Token是否有效
- 过期判断:如果Token已过期或即将过期,触发刷新流程
- 刷新处理:
- 使用Refresh Token获取新的Access Token
- 将新Token更新到后续请求中
- 请求重试:
- 将刷新期间积压的请求加入队列
- 获取新Token后按顺序重试这些请求
- 错误处理:如果刷新失败,执行登出清理操作
3.3 状态管理与并发控制
在处理并发请求时,我们需要特别注意状态管理:
javascript复制// 刷新状态标志
let isRefreshing = false
// 等待队列
let requests = []
const addToQueue = (config) => {
return new Promise((resolve) => {
requests.push((newToken) => {
config.headers.Authorization = `Bearer ${newToken}`
resolve(axios(config))
})
})
}
这个设计确保了:
- 同一时间只有一个刷新请求发出
- 刷新期间的请求不会丢失
- 所有请求最终都能获得有效的Token
4. 完整代码实现与解析
4.1 axios拦截器配置
下面是我在实际项目中使用的完整拦截器实现:
javascript复制// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(
config => {
const token = getAccessToken()
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
return response.data
},
async error => {
const originalRequest = error.config
// Token过期且不是刷新Token的请求
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
