告别频繁登录:教你用Axios实现无感知双Token刷新

告别频繁登录:教你用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实例并添加拦截器:

  1. import axios from 'axios';
  2. const api = axios.create({
  3. baseURL: 'https://api.example.com',
  4. });
  5. // 请求拦截器:添加Access Token
  6. api.interceptors.request.use(
  7. (config) => {
  8. const accessToken = localStorage.getItem('accessToken');
  9. if (accessToken) {
  10. config.headers.Authorization = `Bearer ${accessToken}`;
  11. }
  12. return config;
  13. },
  14. (error) => Promise.reject(error)
  15. );

2.2 响应拦截器:处理Token过期

当服务器返回401错误时,拦截器捕获并尝试刷新Token:

  1. // 响应拦截器:处理401错误
  2. api.interceptors.response.use(
  3. (response) => response,
  4. async (error) => {
  5. const originalRequest = error.config;
  6. // 检查是否为Token过期错误(假设后端返回特定字段)
  7. if (error.response.status === 401 && !originalRequest._retry) {
  8. originalRequest._retry = true;
  9. try {
  10. const refreshToken = localStorage.getItem('refreshToken');
  11. const response = await axios.post('/auth/refresh', { refreshToken });
  12. // 更新Token
  13. const { accessToken, newRefreshToken } = response.data;
  14. localStorage.setItem('accessToken', accessToken);
  15. if (newRefreshToken) {
  16. localStorage.setItem('refreshToken', newRefreshToken);
  17. }
  18. // 重试原请求
  19. originalRequest.headers.Authorization = `Bearer ${accessToken}`;
  20. return api(originalRequest);
  21. } catch (refreshError) {
  22. // 刷新失败,跳转登录页
  23. window.location.href = '/login';
  24. return Promise.reject(refreshError);
  25. }
  26. }
  27. return Promise.reject(error);
  28. }
  29. );

三、双Token刷新的完整实现

3.1 后端API设计

后端需提供两个端点:

  • POST /auth/login:返回Access Token和Refresh Token。
  • POST /auth/refresh:验证Refresh Token并返回新Access Token。

示例响应:

  1. {
  2. "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  3. "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  4. "expiresIn": 900 // 15分钟
  5. }

3.2 前端Token管理

使用localStorage存储Token(注意:HTTPS环境下更安全):

  1. // 登录后存储Token
  2. function handleLogin(response) {
  3. localStorage.setItem('accessToken', response.data.accessToken);
  4. localStorage.setItem('refreshToken', response.data.refreshToken);
  5. }
  6. // 登出时清除Token
  7. function handleLogout() {
  8. localStorage.removeItem('accessToken');
  9. localStorage.removeItem('refreshToken');
  10. }

3.3 刷新Token的防重放机制

为避免并发请求多次触发刷新,可引入锁机制:

  1. let isRefreshing = false;
  2. let subscribers = [];
  3. api.interceptors.response.use(
  4. (response) => response,
  5. async (error) => {
  6. // ...前文代码...
  7. if (error.response.status === 401 && !originalRequest._retry) {
  8. if (!isRefreshing) {
  9. isRefreshing = true;
  10. try {
  11. // 刷新Token逻辑...
  12. } finally {
  13. isRefreshing = false;
  14. // 执行所有订阅的请求
  15. subscribers.forEach((cb) => cb());
  16. subscribers = [];
  17. }
  18. } else {
  19. // 等待刷新完成,暂存请求
  20. return new Promise((resolve) => {
  21. subscribers.push(() => {
  22. originalRequest.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`;
  23. resolve(api(originalRequest));
  24. });
  25. });
  26. }
  27. }
  28. // ...
  29. }
  30. );

四、安全与优化建议

4.1 安全实践

  • HTTPS:所有Token传输必须通过HTTPS。
  • Token存储:避免使用localStorage存储敏感信息,可考虑HttpOnly Cookie。
  • Refresh Token过期:设置合理的Refresh Token有效期,并支持撤销。

4.2 性能优化

  • Token预刷新:在Access Token即将过期时主动刷新,避免等待401错误。
  • 错误重试:对刷新失败的请求进行有限次数的重试。
  • 日志监控:记录Token刷新失败事件,便于排查问题。

五、总结

通过Axios拦截器实现无感知双Token刷新,可以显著提升用户体验,减少因Token过期导致的操作中断。其核心在于:

  1. 双Token设计:Access Token短生命周期,Refresh Token长生命周期。
  2. Axios拦截器:捕获401错误,自动触发刷新流程。
  3. 防重放机制:避免并发刷新导致的多次请求。

这种方案不仅适用于SPA,也可扩展到移动端Hybrid应用。实际开发中,需结合具体业务需求调整Token有效期和刷新策略,同时严格遵循安全规范。

最终效果:用户无需感知Token的刷新过程,即使长时间不操作,系统也能在后台默默维护认证状态,真正实现“永不掉线”的流畅体验。