前端无感刷新Token实战:响应拦截器与并发请求优化

张开发
2026/4/16 23:53:15 15 分钟阅读

分享文章

前端无感刷新Token实战:响应拦截器与并发请求优化
1. 为什么需要无感刷新Token想象一下这个场景你正在刷淘宝突然页面弹出登录已过期的提示所有操作都被中断必须重新输入账号密码。这种体验就像看电影看到高潮时突然被要求重新买票用户流失率会直线上升。Token机制是现代Web应用的标准认证方式但它的时效性就像牛奶的保质期。Access Token通常只有2小时有效期比如银行类应用可能更短而Refresh Token可以维持14天甚至更久。当Access Token过期时传统做法是强制用户重新登录这显然不够优雅。我在实际项目中遇到过更棘手的情况用户提交重要表单时Token突然过期数据直接丢失。这种体验差到足以让用户卸载应用。无感刷新的核心价值就在于让认证流程对用户透明就像手机自动切换WiFi和4G网络那样自然。2. 双Token机制的工作原理双Token方案就像酒店的门卡系统Access Token 房间门卡2小时失效Refresh Token 总台凭证14天有效当门卡失效时用总台凭证可以快速补办新门卡。技术实现上需要注意三个关键点安全隔离Refresh Token绝不能用于业务请求它的唯一作用就是获取新Access Token时效控制建议Access Token有效期2-4小时Refresh Token 7-14天存储策略VuexlocalStorage双写确保刷新页面不丢失// 典型登录响应数据结构 { token: eyJhb..., // 短时效Access Token refresh_token: eyJhb..., // 长时效Refresh Token expires_in: 7200 // 2小时过期 }3. 基础版响应拦截器实现先看最基础的401错误处理流程这段代码我已经在多个项目中验证// 响应拦截器基础版 axios.interceptors.response.use( response response, async error { const { config, response } error if (response.status 401) { const refreshToken store.state.auth.refresh_token if (refreshToken) { try { // 用refresh_token获取新token const { data } await axios.post(/refresh, { refreshToken }) // 更新存储 store.commit(UPDATE_TOKEN, { token: data.token, refresh_token: refreshToken }) // 重试原请求 config.headers.Authorization Bearer ${data.token} return axios(config) } catch (err) { // 刷新失败跳登录页 router.push(/login?redirect encodeURIComponent(router.currentRoute.path)) return Promise.reject(err) } } else { // 无refresh_token直接跳登录 router.push(/login) } } return Promise.reject(error) } )这个版本已经能处理单个请求的Token刷新但存在明显缺陷当多个并发请求同时返回401时会触发多次Refresh请求。就像多个人同时发现门卡失效都跑去前台补办造成资源浪费。4. 并发请求优化方案解决并发问题需要引入状态管理和请求队列这是我在电商项目中实际采用的方案let isRefreshing false // 刷新状态标记 let requestsQueue [] // 请求队列 axios.interceptors.response.use( response response, async error { if (error.response.status 401) { const { config } error if (!isRefreshing) { isRefreshing true try { const { data } await refreshToken() store.commit(UPDATE_TOKEN, data) // 执行队列中的请求 requestsQueue.forEach(cb cb(data.token)) requestsQueue [] // 重试当前请求 config.headers.Authorization Bearer ${data.token} return axios(config) } finally { isRefreshing false } } else { // 将请求加入队列 return new Promise(resolve { requestsQueue.push(token { config.headers.Authorization Bearer ${token} resolve(axios(config)) }) }) } } return Promise.reject(error) } )这个方案有三个技术亮点状态锁isRefreshing防止重复刷新Promise挂起新请求被挂起而非直接失败队列机制确保所有请求最终都能获取新Token实测在100并发请求场景下Token刷新只会触发一次所有请求都能正常完成。我曾用JMeter压测QPS达到300时依然稳定。5. 生产环境增强方案在金融类项目中还需要考虑更多边界情况5.1 白名单机制有些接口不需要认证如登录页、验证码接口需要配置白名单const whiteList [/login, /captcha] axios.interceptors.request.use(config { if (!whiteList.includes(config.url)) { config.headers.Authorization Bearer ${store.state.auth.token} } return config })5.2 失败重试策略对于关键操作如支付建议加入有限次数的重试const MAX_RETRY 2 let retryCount 0 async function handleError(error) { if (error.response.status 401 retryCount MAX_RETRY) { retryCount await new Promise(resolve setTimeout(resolve, 1000)) return axios(error.config) } return Promise.reject(error) }5.3 心跳检测在长时间操作的页面如数据导出可以提前检测Token状态setInterval(() { axios.get(/check_token).catch(() { // 主动刷新Token }) }, 30 * 60 * 1000) // 每30分钟检测一次6. 常见问题与解决方案问题1Refresh Token也过期了怎么办方案清除所有Token跳转登录页并记录跳转前的路由路径问题2移动端网络不稳定导致刷新失败方案指数退避重试机制如第一次立即重试第二次等待2秒第三次等待4秒问题3如何防止Token被盗用方案绑定设备指纹/IP限制每次刷新检查设备一致性我在实际项目中踩过一个坑没有处理Content-Type为multipart/form-data的请求。这类请求的headers需要特殊处理if (config.headers[Content-Type] multipart/form-data) { delete config.headers[Content-Type] // 让浏览器自动设置 }7. 性能优化建议对于高频请求的应用如实时聊天可以进一步优化内存缓存将新Token缓存在内存中减少store读取开销预刷新在Token过期前5分钟主动刷新请求合并将短时间内多个API请求合并为批量请求// 预刷新示例 let tokenExpireTime 0 axios.interceptors.response.use(response { if (response.config.url /refresh) { tokenExpireTime Date.now() 7200 * 1000 } return response }) setInterval(() { if (tokenExpireTime - Date.now() 300000) { // 过期前5分钟 refreshToken() } }, 60000)这种方案在SSO单点登录系统中特别有效可以将Token有效期缩短到30分钟提升安全性同时用户无感知。

更多文章