告别频繁登录:教你用Axios实现无感知双Token刷新
在Web开发中,用户认证是绕不开的核心环节。传统的JWT(JSON Web Token)认证机制虽然简单高效,但面临一个棘手问题:Token过期后,用户必须重新登录,尤其是在SPA(单页应用)中,这种体验非常不友好。本文将深入探讨如何通过Axios拦截器实现无感知双Token刷新,彻底告别频繁登录的困扰。
一、为什么需要双Token刷新?
1.1 单Token的局限性
传统的JWT认证通常使用单个Token,该Token包含用户信息及过期时间(如exp字段)。当Token过期后,服务器会返回401未授权错误,前端需要引导用户重新登录。这种模式在以下场景中表现不佳:
- 用户正在操作:填写表单时突然被强制跳转登录页,数据丢失风险高。
- 移动端体验差:手机端频繁弹出登录框,影响用户体验。
- 高频率操作场景:如金融交易系统,中断操作可能导致业务损失。
1.2 双Token的优势
双Token机制通过引入Refresh Token解决上述问题:
- Access Token:短有效期(如15分钟),用于日常API请求。
- Refresh Token:长有效期(如7天),用于获取新的Access Token。
当Access Token过期时,系统自动使用Refresh Token获取新Token,全程无需用户干预。
二、Axios拦截器:无感知刷新的核心
Axios的请求/响应拦截器是实现无感知刷新的关键。通过拦截401错误,触发Refresh Token流程,并在成功后重试原请求。
2.1 拦截器基础配置
首先,配置Axios实例并添加拦截器:
import axios from 'axios';const api = axios.create({baseURL: 'https://api.example.com',});// 请求拦截器:添加Access Tokenapi.interceptors.request.use((config) => {const accessToken = localStorage.getItem('accessToken');if (accessToken) {config.headers.Authorization = `Bearer ${accessToken}`;}return config;},(error) => Promise.reject(error));
2.2 响应拦截器:处理Token过期
当服务器返回401错误时,拦截器捕获并尝试刷新Token:
// 响应拦截器:处理401错误api.interceptors.response.use((response) => response,async (error) => {const originalRequest = error.config;// 检查是否为Token过期错误(假设后端返回特定字段)if (error.response.status === 401 && !originalRequest._retry) {originalRequest._retry = true;try {const refreshToken = localStorage.getItem('refreshToken');const response = await axios.post('/auth/refresh', { refreshToken });// 更新Tokenconst { accessToken, newRefreshToken } = response.data;localStorage.setItem('accessToken', accessToken);if (newRefreshToken) {localStorage.setItem('refreshToken', newRefreshToken);}// 重试原请求originalRequest.headers.Authorization = `Bearer ${accessToken}`;return api(originalRequest);} catch (refreshError) {// 刷新失败,跳转登录页window.location.href = '/login';return Promise.reject(refreshError);}}return Promise.reject(error);});
三、双Token刷新的完整实现
3.1 后端API设计
后端需提供两个端点:
POST /auth/login:返回Access Token和Refresh Token。POST /auth/refresh:验证Refresh Token并返回新Access Token。
示例响应:
{"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","expiresIn": 900 // 15分钟}
3.2 前端Token管理
使用localStorage存储Token(注意:HTTPS环境下更安全):
// 登录后存储Tokenfunction handleLogin(response) {localStorage.setItem('accessToken', response.data.accessToken);localStorage.setItem('refreshToken', response.data.refreshToken);}// 登出时清除Tokenfunction handleLogout() {localStorage.removeItem('accessToken');localStorage.removeItem('refreshToken');}
3.3 刷新Token的防重放机制
为避免并发请求多次触发刷新,可引入锁机制:
let isRefreshing = false;let subscribers = [];api.interceptors.response.use((response) => response,async (error) => {// ...前文代码...if (error.response.status === 401 && !originalRequest._retry) {if (!isRefreshing) {isRefreshing = true;try {// 刷新Token逻辑...} finally {isRefreshing = false;// 执行所有订阅的请求subscribers.forEach((cb) => cb());subscribers = [];}} else {// 等待刷新完成,暂存请求return new Promise((resolve) => {subscribers.push(() => {originalRequest.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`;resolve(api(originalRequest));});});}}// ...});
四、安全与优化建议
4.1 安全实践
- HTTPS:所有Token传输必须通过HTTPS。
- Token存储:避免使用
localStorage存储敏感信息,可考虑HttpOnlyCookie。 - Refresh Token过期:设置合理的Refresh Token有效期,并支持撤销。
4.2 性能优化
- Token预刷新:在Access Token即将过期时主动刷新,避免等待401错误。
- 错误重试:对刷新失败的请求进行有限次数的重试。
- 日志监控:记录Token刷新失败事件,便于排查问题。
五、总结
通过Axios拦截器实现无感知双Token刷新,可以显著提升用户体验,减少因Token过期导致的操作中断。其核心在于:
- 双Token设计:Access Token短生命周期,Refresh Token长生命周期。
- Axios拦截器:捕获401错误,自动触发刷新流程。
- 防重放机制:避免并发刷新导致的多次请求。
这种方案不仅适用于SPA,也可扩展到移动端Hybrid应用。实际开发中,需结合具体业务需求调整Token有效期和刷新策略,同时严格遵循安全规范。
最终效果:用户无需感知Token的刷新过程,即使长时间不操作,系统也能在后台默默维护认证状态,真正实现“永不掉线”的流畅体验。