前端实现无感刷新Token的方法与避坑指南

 更新时间:2026年03月25日 08:43:40   作者:前端Hardy  
实现token无感刷新对于前端来说是一项非常常用的技术,今天,我们就来彻底搞懂如何真正实现无感刷新Token以及为什么 90% 的实现都有致命缺陷

刷新 Token 不是“过期就重新登录”,而是让用户毫无感知地继续使用

可惜,大多数项目还在用 401 跳登录 粗暴处理——这根本不是用户体验,这是放弃治疗。

在现代 Web 应用中,用户登录后通常会获得一对 Token:

  • Access Token(短期有效,如 15 分钟)
  • Refresh Token(长期有效,如 7 天)

当 Access Token 过期时,理想状态是:前端自动用 Refresh Token 换取新 Token,并重试原请求——整个过程用户无感,页面不跳转、操作不中断。

但现实呢?

“Token 过期 → 弹出登录框 → 用户骂一句‘怎么又登出了’ → 关掉页面走人。”

今天,我们就来彻底搞懂:如何真正实现“无感刷新”Token?为什么 90% 的实现都有致命缺陷?

错误做法一:在每个接口里手动判断 401

// 千万别这么写!
fetch('/api/user')
  .then(res => {
    if (res.status === 401) {
      // 重新登录 or 刷新 token?
      window.location.href = '/login';
    }
  });

问题在哪?

  • 每个接口都要重复写逻辑;
  • 如果多个请求同时 401,会触发多次刷新,甚至多次跳登录;
  • 完全无法做到“无感”

错误做法二:全局拦截 401 后直接刷新 Token 并重试一次

这是目前最“主流”的错误方案:

// 伪代码:看似聪明,实则危险
axios.interceptors.response.use(
  res => res,
  async (error) => {
    if (error.response.status === 401) {
      const newToken = await refreshToken(); // 获取新 token
      saveToken(newToken);
      
      // 用新 token 重试原请求
      return axios(error.config);
    }
  }
);

表面看没问题,但隐藏三大坑

坑 1:并发请求雪崩

当页面刚加载,10 个接口同时发起,而此时 Token 已过期 ——10 个请求全部返回 401 → 触发 10 次 refreshToken() → 后端收到 10 个刷新请求!

后果:

  • 后端可能拒绝重复刷新(安全策略);
  • Refresh Token 被提前消耗,后续真失效;
  • 用户反而被踢下线。

坑 2:Refresh Token 泄露风险

如果前端把 Refresh Token 存在 localStorage,一旦 XSS 攻击成功,攻击者可长期盗用账号。

安全最佳实践:Refresh Token 应仅存于 HttpOnly Cookie,前端不可读!

但上述方案要求前端“拿到新 token”,这就逼你把 Refresh Token 暴露给 JS —— 安全与功能不可兼得?

坑 3:无限重试死循环

如果 refreshToken() 本身也返回 401(比如 Refresh Token 也过期了),重试原请求 → 又 401 → 再刷新 → 再 401 → ……

浏览器卡死,内存飙升。

正确方式:用“锁机制 + 队列 + 安全存储”三位一体

要实现真正的无感刷新,必须同时解决:

  • 并发控制(只刷一次)
  • 安全存储(Refresh Token 不暴露给 JS)
  • 失败兜底(Refresh 失败时优雅降级)

第一步:后端配合 —— Refresh Token 存 HttpOnly Cookie

HTTP/1.1 200 OK
Set-Cookie: refreshToken=abc123; HttpOnly; Secure; SameSite=Strict; Path=/auth

前端永远拿不到 refreshToken,但每次请求会自动携带。

第二步:前端实现“单例刷新锁 + 请求队列”

let isRefreshing = false;
let refreshPromise = null;
const failedQueue = [];

// 重试队列中的请求
const processQueue = (error, token = null) => {
  failedQueue.forEach(({ resolve, reject }) => {
    if (error) {
      reject(error);
    } else {
      resolve(token);
    }
  });
  failedQueue.length = 0;
};

axios.interceptors.response.use(
  response => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        // 已在刷新中,将请求加入队列,等待新 token
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        }).then(token => {
          originalRequest.headers['Authorization'] = `Bearer ${token}`;
          return axios(originalRequest);
        });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      try {
        // 调用刷新接口(后端从 Cookie 读 refreshToken)
        const { data } = await axios.post('/auth/refresh');
        const newAccessToken = data.accessToken;

        // 通知所有排队的请求
        processQueue(null, newAccessToken);

        // 重试当前请求
        originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
        return axios(originalRequest);
      } catch (refreshError) {
        // 刷新失败:清空本地身份,跳转登录
        clearAuth();
        processQueue(refreshError, null);
        window.location.href = '/login';
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
        refreshPromise = null;
      }
    }

    return Promise.reject(error);
  }
);

关键设计解析

机制作用
isRefreshing 锁确保同一时间只发起一次刷新
failedQueue 队列缓存所有因 401 失败的请求,等新 token 到手后批量重试
_retry 标记防止重试后的请求再次进入刷新逻辑
HttpOnly Cookie保护 Refresh Token 不被 XSS 窃取

安全补充:前端 Token 存储建议

Token 类型推荐存储方式原因
Access Token内存(JS 变量)或 sessionStorage短期有效,避免持久化泄露
Refresh TokenHttpOnly Cookie前端不可读,防 XSS

切勿将任何 Token 存入 localStorage!这是 XSS 攻击的黄金目标。

如何测试你的刷新逻辑

  • 手动将 Access Token 设为过期;
  • 快速点击多个按钮,触发并发请求;
  • 观察 Network 面板:
    • 是否只调用了一次 /auth/refresh
    • 所有原请求是否最终成功?
  • 模拟 Refresh Token 失效,是否跳转登录?

结语

“无感刷新 Token”不是炫技,而是对用户体验和系统安全的基本尊重。那些让用户频繁重新登录的产品,不是技术做不到,而是没把用户当回事

真正的专业,藏在细节里:一个锁、一个队列、一个 HttpOnly Cookie —— 就是 10% 正确方案 与 90% 错误实现的分水岭。

你的项目还在用“401 就跳登录”吗?是时候升级了。

欢迎转发给那个总说“Token 过期就让用户重新登录”的同事。

到此这篇关于前端实现无感刷新Token的方法与避坑指南的文章就介绍到这了,更多相关前端无感刷新token内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaScript中常见的Polyfill示例详解

    JavaScript中常见的Polyfill示例详解

    这篇文章主要介绍了JavaScript中常见Polyfill的相关资料,Polyfill是一种代码,用于在旧版浏览器中实现不支持的现代JavaScript功能,以确保跨浏览器兼容性和代码统一性,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-04-04
  • js实现使用输入input和改变change事件模拟手动输入

    js实现使用输入input和改变change事件模拟手动输入

    聚焦于JavaScript中的输入模拟技术,本指南将带你探索如何使用input和change事件来创造逼真的手动输入效果,通过简单的代码实现,你将掌握这一实用的技巧,为你的Web应用增添交互的乐趣,需要的朋友可以参考下
    2024-03-03
  • 移动端手指放大缩小插件与js源码

    移动端手指放大缩小插件与js源码

    这篇文章主要介绍了移动端手指放大缩小插件与js源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • 原生js实现商品筛选功能

    原生js实现商品筛选功能

    这篇文章主要为大家详细介绍了原生js实现商品筛选,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • 微信小程序配置视图层数据绑定相关示例

    微信小程序配置视图层数据绑定相关示例

    这篇文章主要为大家介绍了微信小程序配置视图层数据绑定相关示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪<BR>
    2022-04-04
  • 在 JavaScript 中保留小数点后两位的方法

    在 JavaScript 中保留小数点后两位的方法

    在 JavaScript 中,有多种方法可以保留小数点后两位,本文给大家分享比较常用的方法,文末给大家介绍了实现数据格式化保留两位小数的多种方法,感兴趣的朋友一起看看吧
    2023-10-10
  • javascript禁止访客复制网页内容的实现代码

    javascript禁止访客复制网页内容的实现代码

    这篇文章主要介绍了javascript禁止访客复制网页内容的方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-08-08
  • js怎么判断是否是数组的六种方法小结

    js怎么判断是否是数组的六种方法小结

    本文主要介绍了js怎么判断是否是数组的六种方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 微信小程序网络请求封装示例

    微信小程序网络请求封装示例

    这篇文章主要介绍了微信小程序网络请求封装示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • js生成随机颜色方法代码分享(三种)

    js生成随机颜色方法代码分享(三种)

    本文主要分享了js三种生成随机颜色方法代码,具有一定的参考价值,需要的朋友一起来看下吧
    2016-12-12

最新评论