深度拆解前端双Token无感刷新的实现机制

 更新时间:2026年03月17日 09:02:32   作者:发现一只大呆瓜  
在前后端分离的项目中,为了安全,Token 通常会设置有效期,本文将深度拆解 “双 Token 无感刷新” 的实现机制,有需要的小伙伴可以跟随小编一起学习一下

前言

在前后端分离的项目中,为了安全,Token 通常会设置有效期。但如果 Token 过期时强制用户重新登录,会极大地破坏用户体验。如何做到在用户毫无察觉的情况下,自动完成 Token 的续期?本文将深度拆解 “双 Token 无感刷新” 的实现机制。

一、 为什么需要“无感刷新”?

举个简单例子,你正在某 App 编辑内容,中途切出几分钟,再切回来时,直接弹出登录页,提示“登录已过期,请重新登录”,这种场景很容易让用户流失。

传统的单 Token 方案存在一个两难境地:

  • 有效期过短:用户操作频繁,动不动就跳回登录页,用户体验极差。
  • 有效期过长:Token 一旦被截获,风险极高。

解决方案:双 Token 机制

  • access_token:访问令牌。有效期短(如 1 小时),每次接口请求都携带,降低泄露风险。
  • refresh_token:刷新令牌。有效期长(如 7 天),仅用于 access_token 过期时换取新令牌。

只要用户在 7 天内活跃过,系统就能通过 refresh_token 自动“续命”,实现长效无感登录。

二、 核心流程设计

正常请求:前端携带 access_token 访问。

触发过期:后端返回 401 Unauthorized

判断逻辑

  • 如果是普通接口报 401:说明 access_token 失效,尝试刷新。
  • 如果是刷新接口报 401:说明 refresh_token 也失效了,强制重新登录。

无感替换:前端自动调用刷新接口,获取新 Token 覆盖本地存储,并重新发起之前失败的请求。

三、 细节攻坚:如何处理并发请求?

痛点:如果页面同时发出了 5 个请求,而此时 Token 刚好过期,会导致这 5 个请求同时触发“刷新 Token”的操作,造成资源浪费甚至后端异常。

解决策略

  • 状态锁 (refreshing) :记录当前是否正在刷新中。
  • 任务队列 (queue) :在刷新期间到达的请求,先暂存起来,不直接报错。
  • 批量回放:等待 Token 刷新成功后,依次执行队列里的请求,实现“无感”衔接。

四、 代码实现 (Axios 拦截器)

以下是基于 Axios 的完整工程化实现:

import axios, { AxiosRequestConfig } from 'axios';

interface PendingTask {
    config: AxiosRequestConfig;
    resolve: Function;
}

let refreshing = false; // 状态锁:标志是否正在刷新 Token
let queue: PendingTask[] = []; // 请求队列:暂存 Token 刷新期间的请求

const axiosInstance = axios.create({
    baseURL: '/api'
});

// 1. 请求拦截器:自动注入 Token
axiosInstance.interceptors.request.use((config) => {
    const accessToken = localStorage.getItem('access_token');
    if (accessToken && config.headers) {
        config.headers.authorization = `Bearer ${accessToken}`;
    }
    return config;
});

// 2. 响应拦截器:处理 Token 过期
axiosInstance.interceptors.response.use(
    (response) => response,
    async (error) => {
        const { data, config } = error.response;

        // 情况 A:正在刷新 Token 中,将后续请求存入队列
        if (refreshing) {
            return new Promise((resolve) => {
                queue.push({ config, resolve });
            });
        }

        // 情况 B:access_token 过期 (状态码 401 且非刷新接口本身)
        if (data.statusCode === 401 && !config.url.includes('/refresh')) {
            refreshing = true;
            
            try {
                const res = await refreshToken();
                refreshing = false;

                if (res.status === 200) {
                    // 核心逻辑:Token 刷新成功,回放队列中的所有请求
                    queue.forEach(({ config, resolve }) => {
                        resolve(axiosInstance(config));
                    });
                    queue = []; // 清空队列
                    
                    // 执行当前触发刷新的那个请求
                    return axiosInstance(config);
                }
            } catch (err) {
                refreshing = false;
                queue = [];
                // 情况 C:refresh_token 也过期了,彻底清除登录态
                localStorage.clear();
                window.location.href = '/login';
                return Promise.reject(err);
            }
        }

        return Promise.reject(error);
    }
);

/**
 * 刷新 Token 的异步方法
 */
async function refreshToken() {
    const res = await axios.get('/api/refresh', {
        params: {
            token: localStorage.getItem('refresh_token')
        }
    });
    // 更新本地存储
    localStorage.setItem('access_token', res.data.accessToken);
    localStorage.setItem('refresh_token', res.data.refreshToken);
    return res;
}

五、 注意事项

  • 并发请求的 Promise 挂起:在 refreshingtrue 时,返回一个不带 resolvenew Promise 是关键,它能让 Axios 请求处于 pending 状态。
  • 错误捕获refreshToken 接口本身报错(如 500 或 401)必须妥善处理,直接引导至登录页。
  • 安全性:普通项目中可以使用 localStorage,但在更高要求的项目中,建议配合 HttpOnly Cookie 存储 refresh_token 以防 XSS 攻击。
  • 接口重定向陷阱:确保刷新 Token 的接口不会再次进入 401 拦截死循环。

到此这篇关于深度拆解前端双Token无感刷新的实现机制的文章就介绍到这了,更多相关前端无感刷新token内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Echarts中legend属性使用的方法详解

    Echarts中legend属性使用的方法详解

    Echarts可以帮助我们快速构建柱状图、饼图、条形图,这对于多图形化展示数据来说尤其方便,可帮助我们快速开发,下面这篇文章主要给大家介绍了关于Echarts中legend属性使用的相关资料,需要的朋友可以参考下
    2022-04-04
  • 深入了解TypeScript中的映射类型

    深入了解TypeScript中的映射类型

    在 TypeScript 中,映射类型可以帮助我们避免编写重复的代码,它可以根据现有类型和定义的一些规则来创建新类型。本文我们就来看一下什么是映射类型以及如何构建自己的映射类型
    2022-08-08
  • javascript:文字不间断向左移动的实例代码

    javascript:文字不间断向左移动的实例代码

    这篇文章介绍了javascript:文字不间断向左移动的实例代码,有需要的朋友可以参考一下
    2013-08-08
  • JavaScript中工厂函数与构造函数示例详解

    JavaScript中工厂函数与构造函数示例详解

    这篇文章主要给大家介绍了关于JavaScript中工厂函数与构造函数的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用JavaScript具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05
  • JavaScript学习小结(7)之JS RegExp

    JavaScript学习小结(7)之JS RegExp

    在js中,正则表达式是由一个RegExp对象表示的,RegExp 是正则表达式的缩写。RegExp简单的模式可以是一个单独的字符。更复杂的模式包括了更多的字符,并可用于解析、格式检查、替换等等。可以使用一个RegExp()构造函数来创建RegExp对象,也可以使用直接量语法
    2015-11-11
  • JavaScript如何判断一个对象是空对象(附5种常见方法)

    JavaScript如何判断一个对象是空对象(附5种常见方法)

    在JavaScript中判断对象的类型是开发过程中的一个常见需求,尤其是在处理不确定类型的数据时,这篇文章主要介绍了JavaScript如何判断一个对象是空对象的相关资料,需要的朋友可以参考下
    2025-04-04
  • 关于layui时间回显问题的解决方法

    关于layui时间回显问题的解决方法

    今天小编就为大家分享一篇关于layui时间回显问题的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • JavaScript实现网页动态生成表格

    JavaScript实现网页动态生成表格

    这篇文章主要为大家详细介绍了JavaScript实现网页动态生成表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • js导出txt示例代码

    js导出txt示例代码

    很多新手朋友们都不知道js怎么导出txt,下面有个不错的示例,大家可以参考下
    2014-01-01
  • 微信小程序实现同时上传多张图片

    微信小程序实现同时上传多张图片

    这篇文章主要为大家详细介绍了微信小程序实现同时上传多张图片,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02

最新评论