如何实现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应用用户体验的关键技术之一。通过合理的架构设计和细致的异常处理,可以实现安全、稳定的自动刷新机制。本文介绍的核心实现方案和优化技巧,可以帮助开发者构建更加健壮的身份认证系统。

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

相关推荐
海上彼尚11 分钟前
使用 npm-run-all2 简化你的 npm 脚本工作流
前端·npm·node.js
开发者小天44 分钟前
为什么 /deep/ 现在不推荐使用?
前端·javascript·node.js
如白驹过隙1 小时前
cloudflare缓存配置
前端·缓存
excel1 小时前
JavaScript 异步编程全解析:Promise、Async/Await 与进阶技巧
前端
Jerry说前后端2 小时前
Android 组件封装实践:从解耦到架构演进
android·前端·架构
步行cgn2 小时前
在 HTML 表单中,name 和 value 属性在 GET 和 POST 请求中的对应关系如下:
前端·hive·html
hrrrrb2 小时前
【Java Web 快速入门】十一、Spring Boot 原理
java·前端·spring boot
找不到工作的菜鸟2 小时前
Three.js三大组件:场景(Scene)、相机(Camera)、渲染器(Renderer)
前端·javascript·html
定栓3 小时前
vue3入门-v-model、ref和reactive讲解
前端·javascript·vue.js
专注API从业者3 小时前
基于 Flink 的淘宝实时数据管道设计:商品详情流式处理与异构存储
大数据·前端·数据库·数据挖掘·flink