logo

告别频繁登录:Axios实现无感双Token刷新全攻略

作者:菠萝爱吃肉2025.10.14 02:04浏览量:21

简介:本文详细讲解如何利用Axios拦截器实现无感知双Token刷新机制,彻底解决JWT过期导致的频繁登录问题。通过双Token设计(Access Token+Refresh Token)和自动刷新策略,让用户享受无缝的认证体验。

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

一、问题背景:JWT认证的痛点

在前后端分离架构中,JWT(JSON Web Token)因其无状态特性成为主流认证方案。但传统单Token设计存在致命缺陷:当Access Token过期时,用户必须重新登录获取新Token。这种中断式认证体验严重影响用户体验,尤其在移动端场景下更为突出。

典型问题场景:

  • 用户填写长表单时突然提示Token过期
  • 支付流程进行到关键步骤被迫中断
  • 移动端APP频繁弹出登录窗口

二、双Token设计原理

1. Token分类

  • Access Token:短期有效(如15分钟),用于访问受保护资源
  • Refresh Token:长期有效(如7天),用于获取新的Access Token

2. 工作流程

  1. sequenceDiagram
  2. Client->>Server: 首次登录(获取双Token)
  3. Server-->>Client: 返回Access Token + Refresh Token
  4. Client->>Server: 使用Access Token访问API
  5. Server-->>Client: 返回数据
  6. Client->>Server: Access Token过期
  7. Server-->>Client: 返回401未授权
  8. Client->>Server: 使用Refresh Token获取新Access Token
  9. Server-->>Client: 返回新Access Token
  10. Client->>Server: 重试原API请求

3. 安全性设计

  • Refresh Token存储在HttpOnly Cookie中
  • 每次刷新生成新Token对,旧Refresh Token立即失效
  • 限制Refresh Token使用次数(如最多5次)

三、Axios拦截器实现方案

1. 基础配置

  1. import axios from 'axios';
  2. const apiClient = axios.create({
  3. baseURL: process.env.REACT_APP_API_BASE_URL,
  4. timeout: 10000,
  5. headers: {
  6. 'Content-Type': 'application/json',
  7. }
  8. });

2. 请求拦截器(添加Token)

  1. apiClient.interceptors.request.use(
  2. (config) => {
  3. const accessToken = localStorage.getItem('accessToken');
  4. if (accessToken) {
  5. config.headers.Authorization = `Bearer ${accessToken}`;
  6. }
  7. return config;
  8. },
  9. (error) => {
  10. return Promise.reject(error);
  11. }
  12. );

3. 响应拦截器(处理Token过期)

  1. apiClient.interceptors.response.use(
  2. (response) => response,
  3. async (error) => {
  4. const originalRequest = error.config;
  5. // 仅处理401错误且未重试过的请求
  6. if (error.response?.status === 401 && !originalRequest._retry) {
  7. originalRequest._retry = true;
  8. try {
  9. const refreshToken = getCookie('refreshToken'); // 从Cookie获取
  10. const { data } = await axios.post('/auth/refresh', { refreshToken });
  11. // 更新Token
  12. localStorage.setItem('accessToken', data.accessToken);
  13. // 重试原请求
  14. return apiClient(originalRequest);
  15. } catch (refreshError) {
  16. // 刷新失败,跳转到登录页
  17. window.location.href = '/login';
  18. return Promise.reject(refreshError);
  19. }
  20. }
  21. return Promise.reject(error);
  22. }
  23. );

四、完整实现示例

1. 登录逻辑

  1. const handleLogin = async (credentials) => {
  2. try {
  3. const response = await apiClient.post('/auth/login', credentials);
  4. // 存储Token
  5. localStorage.setItem('accessToken', response.data.accessToken);
  6. document.cookie = `refreshToken=${response.data.refreshToken}; path=/; Secure; HttpOnly; SameSite=Strict`;
  7. // 设置定时器提前刷新(可选)
  8. const expiresIn = response.data.expiresIn;
  9. setTimeout(() => refreshTokenSilently(), expiresIn * 1000 - 30000); // 提前30秒刷新
  10. } catch (error) {
  11. console.error('登录失败:', error);
  12. }
  13. };

2. 静默刷新实现

  1. const refreshTokenSilently = async () => {
  2. const refreshToken = getCookie('refreshToken');
  3. if (!refreshToken) return;
  4. try {
  5. const { data } = await axios.post('/auth/refresh', { refreshToken });
  6. localStorage.setItem('accessToken', data.accessToken);
  7. // 更新后续请求的Token
  8. axios.defaults.headers.common['Authorization'] = `Bearer ${data.accessToken}`;
  9. } catch (error) {
  10. console.error('Token刷新失败:', error);
  11. // 可选:强制登出
  12. }
  13. };

五、最佳实践与注意事项

1. 安全建议

  • Refresh Token必须使用HttpOnly Cookie存储
  • 启用CSRF保护机制
  • 实现Token黑名单机制(可选)
  • 定期更换Refresh Token密钥

2. 性能优化

  • 设置合理的Token过期时间(Access Token 15-30分钟,Refresh Token 7-30天)
  • 避免频繁刷新Token(建议提前30秒刷新)
  • 实现Token缓存机制,减少重复刷新

3. 错误处理

  • 区分Token过期和其他401错误
  • 实现优雅的降级方案(如展示友好提示)
  • 记录Token刷新失败事件用于监控

六、扩展方案:多实例管理

对于大型应用,建议创建多个Axios实例:

  1. // 认证相关实例
  2. const authClient = axios.create({
  3. baseURL: '/auth',
  4. withCredentials: true // 必须启用以发送Cookie
  5. });
  6. // API请求实例
  7. const apiClient = axios.create({
  8. baseURL: '/api',
  9. timeout: 10000
  10. });
  11. // 为每个实例配置独立的拦截器

七、测试策略

1. 单元测试示例

  1. test('should refresh token on 401 error', async () => {
  2. // 模拟401响应
  3. axios.get.mockResolvedValueOnce({ response: { status: 401 } });
  4. // 模拟成功的refreshToken
  5. axios.post.mockResolvedValueOnce({
  6. data: { accessToken: 'new-token' }
  7. });
  8. // 执行测试
  9. const result = await refreshTokenHandler();
  10. // 验证
  11. expect(axios.post).toHaveBeenCalledWith('/auth/refresh', {
  12. refreshToken: expect.any(String)
  13. });
  14. expect(localStorage.setItem).toHaveBeenCalledWith('accessToken', 'new-token');
  15. });

2. 集成测试要点

  • 测试Token过期场景
  • 测试Refresh Token失效场景
  • 测试并发请求时的Token刷新

八、常见问题解决方案

1. 问题:刷新时出现并发请求

解决方案:使用请求队列或锁机制

  1. let isRefreshing = false;
  2. let subscribers = [];
  3. apiClient.interceptors.response.use(
  4. response => response,
  5. error => {
  6. const { config, response } = error;
  7. if (response?.status === 401) {
  8. if (!isRefreshing) {
  9. isRefreshing = true;
  10. refreshToken().then(newToken => {
  11. subscribers.forEach(cb => cb(newToken));
  12. subscribers = [];
  13. });
  14. }
  15. return new Promise(resolve => {
  16. subscribers.push(accessToken => {
  17. config.headers.Authorization = `Bearer ${accessToken}`;
  18. resolve(apiClient(config));
  19. });
  20. });
  21. }
  22. return Promise.reject(error);
  23. }
  24. );

解决方案:

  • 对于不支持Cookie的移动端,使用加密的本地存储
  • 实现短期有效的Refresh Token(如1小时)
  • 结合设备指纹增强安全性

九、总结与展望

通过双Token机制和Axios拦截器的结合,我们实现了:

  1. 用户无感知的Token自动刷新
  2. 显著减少的登录中断次数
  3. 增强的系统安全性

未来演进方向:

  • 引入OAuth 2.0设备授权流
  • 实现基于生物特征的持续认证
  • 探索无Token的认证方案(如WebAuthn)

这种实现方案已在多个生产环境验证,能够有效提升用户留存率和系统安全性。开发者可根据实际需求调整Token有效期和刷新策略,构建最适合自己业务的认证体系。

发表评论

活动