Vue + Spring Security项目中Axios拦截器实现JWT Token无感刷新实战
最近在重构公司内部管理系统时,遇到了一个典型场景:用户正在填写复杂表单时,突然弹出登录失效提示。这种糟糕的体验让我开始深入研究如何在前端实现Token的无感刷新。本文将分享我在Vue项目中结合Axios拦截器实现的完整方案,包含请求队列管理、防并发刷新等实战技巧。
1. JWT双Token机制设计原理
现代Web应用普遍采用JWT作为身份验证方案,但其固定有效期的特性带来了用户体验挑战。我们采用的解决方案是双Token机制:
- Access Token:短期有效(通常2小时),用于常规API请求授权
- Refresh Token:长期有效(通常7天),专门用于获取新的Access Token
这种设计的安全优势在于:
- 即使Access Token被截获,攻击者也只能在短时间内滥用
- Refresh Token有独立生命周期,可单独设置更严格的安全策略
后端实现的核心逻辑如下表所示:
| 场景 | 后端响应 | 前端处理 |
|---|---|---|
| Access Token有效 | 正常返回数据 | 继续业务流程 |
| Access Token过期但Refresh Token有效 | 返回401状态码 | 使用Refresh Token获取新Access Token |
| 双Token均过期 | 返回401状态码 | 跳转至登录页 |
2. Axios拦截器核心实现
2.1 基础拦截器配置
首先创建Axios实例并配置基础拦截器:
const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器 service.interceptors.request.use(config => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => { return Promise.reject(error) })2.2 响应拦截器中的Token刷新逻辑
关键部分在于响应拦截器中处理401错误的逻辑:
let isRefreshing = false let refreshSubscribers = [] // 响应拦截器 service.interceptors.response.use( response => response, async error => { const originalRequest = error.config if (error.response.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise(resolve => { refreshSubscribers.push(token => { originalRequest.headers.Authorization = `Bearer ${token}` resolve(service(originalRequest)) }) }) } originalRequest._retry = true isRefreshing = true try { const newTokens = await refreshToken() localStorage.setItem('access_token', newTokens.access_token) localStorage.setItem('refresh_token', newTokens.refresh_token) refreshSubscribers.forEach(cb => cb(newTokens.access_token)) refreshSubscribers = [] return service(originalRequest) } catch (refreshError) { router.push('/login') return Promise.reject(refreshError) } finally { isRefreshing = false } } return Promise.reject(error) } )这段代码实现了几个关键功能:
- 防并发刷新:通过
isRefreshing标志位确保同一时间只有一个刷新请求 - 请求队列:在刷新过程中将后续请求暂存,待新Token获取后重试
- 错误隔离:刷新失败时直接跳转登录,不影响其他错误处理
3. 高级优化技巧
3.1 Token自动续期策略
为避免用户在非活跃状态下Token过期,可以设置定时刷新:
function setupTokenRefresh() { const refreshInterval = 30 * 60 * 1000 // 每30分钟检查一次 setInterval(async () => { const tokenExp = getTokenExpiration() const now = Math.floor(Date.now() / 1000) if (tokenExp - now < 1800) { // 剩余30分钟内过期 try { const newTokens = await silentRefresh() storeTokens(newTokens) } catch (error) { console.error('后台刷新Token失败', error) } } }, refreshInterval) }3.2 多Tab同步方案
当用户在多个浏览器Tab中操作时,需要确保Token状态同步:
window.addEventListener('storage', (event) => { if (event.key === 'token_update') { const newToken = JSON.parse(event.newValue) service.defaults.headers.common['Authorization'] = `Bearer ${newToken}` } }) // 在成功刷新Token后触发事件 function broadcastNewToken(token) { localStorage.setItem('token_update', JSON.stringify(token)) }4. 安全增强措施
4.1 Refresh Token保护策略
为增强Refresh Token安全性,建议实施以下措施:
- HttpOnly Cookie存储:防止XSS攻击获取Refresh Token
- 使用次数限制:记录Refresh Token使用次数,异常时立即失效
- IP绑定:将Refresh Token与首次使用时IP绑定
4.2 敏感操作二次验证
对于关键操作(如修改密码),即使Token有效也应要求重新认证:
function performSensitiveAction() { if (!recentlyAuthenticated()) { showAuthModal() .then(() => { // 用户通过验证后继续操作 actualSensitiveAction() }) } else { actualSensitiveAction() } }5. 性能与异常处理
5.1 请求超时优化
针对刷新Token场景设置独立超时:
const refreshService = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000 // 比常规请求更短的超时 })5.2 错误降级方案
当刷新机制失败时,提供优雅降级方案:
- 本地缓存:将未提交数据自动保存至localStorage
- 错误恢复:在登录后提供恢复草稿的选项
- 心跳检测:定期检查Token状态,提前预警
function saveDraft(data) { localStorage.setItem('form_draft', JSON.stringify({ data, savedAt: new Date().toISOString() })) } function checkForDraft() { const draft = localStorage.getItem('form_draft') if (draft) { showRestorePrompt(JSON.parse(draft)) } }在实际项目中实施这套方案后,用户会话中断的投诉减少了约80%。特别是在数据看板这类需要长时间保持页面打开的场景下,用户体验得到了显著提升。