如何实现token无感刷新

在现代Web应用中,身份认证是保障系统安全重要环节。基于Token的认证机制因其灵活性、跨域支持等优势被广泛采用。然而,Token的过期问题一直是开发者需要面对的挑战。本文将深入探讨如何实现Token无感刷新机制,提升用户体验的同时保障系统安全性。

🔴 背景与核心概念

🔻 单点登录环境下的Token机制

无感刷新技术必须建立在单点登录(SSO)环境下才有实际意义。在SSO系统中,用户只需登录一次可访问多个相互信任应用系统。这种环境下通常会使用两种Token

  1. Access Token(访问令牌):有效期较短(通常10-20分钟),用于访问受保护资源
  2. Refresh Token(刷新令牌):有效期较长(通常7天至1个月),用于获取新的Access Token

🔻 为什么需要无感刷新?

当Access Token过期时,传统做法是强制用户重新登录,这会导致:

  • 频繁中断用户操作流程
  • 降低用户体验
  • 增加用户认证负担

无感刷新技术正是为了解决这些问题而诞生,它能够在用户无感知的情况下自动完成Token更新

🔴 技术实现方案

🔻 基础架构设计

实现无感刷新需要前后端协同工作,但主要逻辑集中在客户端(前端):

  1. 登录成功时 :客户端接收存储Access TokenRefresh Token
  2. API请求时携带当前有效的Access Token
  3. Token过期时自动使用Refresh Token获取新的Access Token
  4. 重新请求 :用新Token重试原始请求

🔻 前端实现代码解析

以下是一个基于Axios的实现示例:

javascript 复制代码
// 创建axios实例
const instance = axios.create({
  baseURL: 'https://api.example.com'
});

// 请求拦截器 - 添加Token到请求头
instance.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截器 - 处理Token刷新逻辑
instance.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    
    // 判断是否为401错误且不是刷新Token的请求
    if (error.response.status === 401 && !originalRequest._isRetry) {
      originalRequest._isRetry = true;
      
      try {
        // 尝试刷新Token
        const { data } = await refreshToken();
        
        // 存储新Token
        localStorage.setItem('access_token', data.access_token);
        localStorage.setItem('refresh_token', data.refresh_token);
        
        // 修改原始请求的Token头
        originalRequest.headers.Authorization = `Bearer ${data.access_token}`;
        
        // 重新发起原始请求
        return instance(originalRequest);
      } catch (refreshError) {
        // 刷新失败,跳转登录页
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

// 刷新Token的函数
let refreshPromise = null;
async function refreshToken() {
  if (!refreshPromise) {
    const refreshToken = localStorage.getItem('refresh_token');
    refreshPromise = axios.post('/auth/refresh', { refresh_token: refreshToken })
      .finally(() => {
        refreshPromise = null; // 无论成功失败都重置
      });
  }
  return refreshPromise;
}

🔴 关键问题与解决方案

🔻 1. 防止重复刷新

多个请求同时遇到Token过期时,需要防止并发刷新请求

解决方案 :使用Promise缓存机制确保同一时间只有一个刷新请求在进行

javascript 复制代码
let refreshPromise = null;

async function refreshToken() {
  if (!refreshPromise) {
    const refreshToken = localStorage.getItem('refresh_token');
    refreshPromise = axios.post('/auth/refresh', { refresh_token })
      .finally(() => {
        refreshPromise = null; // 重置Promise
      });
  }
  return refreshPromise;
}

🔻 2. 避免无限循环

当Refresh Token也过期时,系统可能陷入无限刷新循环:

解决方案:区分普通请求和刷新请求,仅对普通请求执行刷新逻辑。

javascript 复制代码
// 在刷新请求中添加特殊标记
async function refreshToken() {
  const config = {
    _isRefresh: true // 自定义标记
  };
  return axios.post('/auth/refresh', { refresh_token }, config);
}

// 在拦截器中检查
if (error.response.status === 401 && !originalRequest._isRefresh) {
  // 执行刷新逻辑
}

🔻 3. 竞态条件处理

当第一个刷新请求未完成时,后续请求应等待而非发起新的刷新:

解决方案:使用共享的Promise实例,所有等待中的请求共享同一个刷新结果。

🔴 进阶优化方案

🔻 1. 跨标签页同步

多标签页环境下,应共享刷新状态:

javascript 复制代码
// 使用BroadcastChannel API同步状态
const channel = new BroadcastChannel('token_refresh');

channel.onmessage = (event) => {
  if (event.data.type === 'token_updated') {
    // 更新本地Token
    localStorage.setItem('access_token', event.data.token);
  }
};

// 刷新成功后广播通知
channel.postMessage({
  type: 'token_updated',
  token: newToken
});

🔻 2. 预刷新机制

在Token接近过期时提前刷新,避免请求失败:

javascript 复制代码
function checkTokenExpiry() {
  const token = localStorage.getItem('access_token');
  const { exp } = decodeToken(token); // 解析JWT获取过期时间
  const now = Date.now() / 1000;
  
  // 在过期前5分钟触发刷新
  if (exp - now < 300) {
    refreshToken();
  }
}

// 定时检查(如每分钟一次)
setInterval(checkTokenExpiry, 60000);

🔴 安全注意事项

  1. Refresh Token保护:应严格保护Refresh Token,建议设置HttpOnly、Secure和SameSite属性
  2. 单次使用:Refresh Token应设计为单次使用,刷新后失效
  3. 绑定设备:Refresh Token可与设备指纹绑定,增加安全性
  4. 撤销机制:提供管理员接口可主动撤销Refresh Token

🔴 后端实现

下载jwt包,配置redis

  1. 登录流程

    • 用户提交凭证 → 验证通过 → 返回双Token
    • Access Token用于常规请求(短有效期)
    • Refresh Token用于获取新Access Token(长有效期)
  2. 无感刷新流程

    • 前端检测到401错误 → 使用Refresh Token获取新Access Token
    • 自动重试原始请求 → 用户无感知
  3. 安全机制

    • Refresh Token白名单验证
    • Token黑名单机制
    • 短期Access Token减少泄露风险

📢:所有相关token的操作都使用redis进行存储和判断以及删除。

🔴 总结

Token无感刷新技术是提升现代Web应用用户体验的关键技术之一。通过合理的架构设计和细致的异常处理,可以实现安全、稳定的自动刷新机制。本文介绍的核心实现方案和优化技巧,可以帮助开发者构建更加健壮的身份认证系统。

值得强调的是,无感刷新必须与单点登录系统配合使用才有实际意义。在实际项目中,开发者还需要根据具体业务需求和安全要求进行适当调整和扩展。

相关推荐
前端小巷子3 分钟前
跨域问题解决方案:开发代理
前端·javascript·面试
前端_逍遥生3 分钟前
Chrome 插件开发到发布完整指南:从零开始打造 TTS 朗读助手
前端·chrome
Mintopia4 分钟前
Three.js 材质与灯光:一场像素级的光影华尔兹
前端·javascript·three.js
天涯学馆5 分钟前
JavaScript 跨域、事件循环、性能优化面试题解析教程
前端·javascript·面试
掘金一周14 分钟前
别再用 100vh 了!移动端视口高度的终极解决方案| 掘金一周7.3
前端·后端
晴殇i16 分钟前
CSS 迎来重大升级:Chrome 137 支持 if () 条件函数,样式逻辑从此更灵活
前端·css·面试
咚咚咚ddd18 分钟前
cursor mcp实践:网站落地页性能检测报告(browser-tools)
前端
MiyueFE19 分钟前
让我害怕的 TypeScript 类型 — — 直到我学会了这 3 条规则
前端·typescript
Hilaku19 分钟前
2025年,每个前端都应该了解的CSS选择器:`:has()`, `:is()`, `:where()`
前端·css
OLong22 分钟前
2025年最强React插件,支持大量快捷操作
前端·react.js·visual studio code