深度拆解前端双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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于js JWT的前端存储新思路详解

    关于js JWT的前端存储新思路详解

    JSON Web Token (JWT)是目前最流行的跨域身份验证解决方案,这篇文章主要介绍了关于js JWT的前端存储新思路的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-10-10
  • 教学演示-UBB,剪贴板,textRange及其他

    教学演示-UBB,剪贴板,textRange及其他

    [红色]教学演示-UBB,剪贴板,textRange及其他...
    2006-11-11
  • ECMAScript中var let const常见问题及区别详解

    ECMAScript中var let const常见问题及区别详解

    这篇文章主要为大家介绍了ECMAScript中var let const常见问题及区别详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • JavaScript判断数组是否包含指定元素的方法

    JavaScript判断数组是否包含指定元素的方法

    这篇文章主要介绍了JavaScript判断数组是否包含指定元素的方法,涉及javascript中contains方法的使用技巧,需要的朋友可以参考下
    2015-07-07
  • 微信小程序iBeacon测距及稳定程序的实现解析

    微信小程序iBeacon测距及稳定程序的实现解析

    这篇文章主要介绍了微信小程序iBeacon测距及稳定程序的实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • JavaScript学习笔记之DOM基础操作实例小结

    JavaScript学习笔记之DOM基础操作实例小结

    这篇文章主要介绍了JavaScript学习笔记之DOM基础操作,结合实例形式总结分析了javascript针对dom元素节点、属性的相关获取、设置等操作技巧,需要的朋友可以参考下
    2019-01-01
  • 判断颜色是否合法的正则表达式(详解)

    判断颜色是否合法的正则表达式(详解)

    下面小编就为大家带来一篇判断颜色是否合法的正则表达式(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • 原生js页面滚动延迟加载图片

    原生js页面滚动延迟加载图片

    这篇文章主要介绍了原生js页面滚动延迟加载图片的相关资料,现在瀑布流效果大行其道,各种网站都有应用,尤其是专业的图片类型的网站,感兴趣的朋友可以参考下
    2015-12-12
  • Javascript中Eval函数的使用说明

    Javascript中Eval函数的使用说明

    JavaScript有许多小窍门来使编程更加容易。 其中之一就是eval()函数,这个函数可以把一个字符串当作一个JavaScript表达式一样去执行它。
    2008-10-10
  • js实现淘宝固定侧边栏

    js实现淘宝固定侧边栏

    这篇文章主要为大家详细介绍了js实现淘宝固定侧边栏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07

最新评论