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

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

相关推荐
xiaotao131几秒前
Vite 完全学习指南
前端·vite·前端打包
军军君0114 分钟前
Three.js基础功能学习十五:智能黑板实现实例二
开发语言·前端·javascript·vue.js·3d·threejs·三维
IT枫斗者22 分钟前
构建具有执行功能的 AI Agent:基于工作记忆的任务规划与元认知监控架构
android·前端·vue.js·spring boot·后端·架构
hotlinhao23 分钟前
Nginx rewrite last 与 redirect 的区别——Vue history 模式短链接踩坑记录
前端·vue.js·nginx
ZC跨境爬虫25 分钟前
海南大学交友平台开发实战day7(实现核心匹配算法+解决JSON请求报错问题)
前端·python·算法·html·json
下北沢美食家28 分钟前
CSS面试题2
前端·css
weixin_4617694035 分钟前
npm create vue@latest 错误
前端·vue.js·npm
WindrunnerMax36 分钟前
从零实现富文本编辑器#13-React非编辑节点的内容渲染
前端·架构·github
四千岁36 分钟前
Ollama+OpenWebUI 最佳组合:本地大模型可视化交互方案
前端·javascript·后端
写不来代码的草莓熊39 分钟前
el-date-picker ,自定义输入数字自动转换显示yyyy-mm-dd HH:mm:ss格式
前端·javascript·vue.js