利用Axios实现无感知双Token刷新的详细教程

作者:青山有信时间:2026-11-01 03:11:12浏览:117

本文详细讲解如何在Vue3项目中利用Axios实现无感知的双Token(Access Token与Refresh Token)自动刷新机制。通过配置请求与响应拦截器,结合单例Promise队列处理并发过期请求,有效解决Token频繁失效导致的用户重复登录问题,提升系统安全性与用户体验。

一、引言

在现代Web系统中,基于Token的身份认证已成为保障用户数据安全的核心机制。然而,许多系统在Token刷新策略上存在缺陷,导致用户频繁遭遇登录失效,体验较差。随着安全策略对Token有效期的缩短,这种问题愈发突出。本文旨在结合Vue3与Axios技术栈,利用Promise异步机制,构建一套用户无感知的自动化Token刷新方案,从而在保障系统安全性的同时,显著提升应用的稳定性与用户交互体验。

二、示意图

一、引言

三、具体实现

在明确基本流程后,实际编码工作看似简洁,但为确保Token刷新机制的稳健性与可靠性,仍需仔细处理多个关键细节。

  • Token存储与管理:首要任务是确定Access Token与Refresh Token的安全存储方式。这涉及浏览器存储策略的选择,例如localStorage或sessionStorage。本文不深入探讨存储策略的优劣,直接采用localStorage进行Token持久化存储。
  • 请求拦截器配置:在Axios中配置请求拦截器,旨在每次发起HTTP请求前校验Token的有效性。若检测到Token已过期,则立即触发刷新流程。此环节需特别注意防止因并发请求导致的重复刷新问题。
  • Token刷新响应逻辑:当Access Token过期时,系统需使用Refresh Token向服务器请求新的Access Token。此外,还需妥善处理刷新失败的场景,例如当Refresh Token也失效时,应引导用户重新进行登录操作。
  • 引入队列机制:在Token刷新期间,可能有多个前端请求同时发出。为避免重复发起刷新请求,需引入队列机制,使其他请求在刷新完成前处于挂起状态,待新Token获取后再继续执行。
  • 错误处理与用户体验:最后,需完善全流程的错误处理逻辑,包括刷新失败后的重试策略及错误提示,确保即使发生异常,用户感知到的体验影响降至最低。

通过上述步骤的实施,可构建出一个对用户透明、稳定可靠的双Token刷新机制,从而增强应用的安全防护能力并优化用户体验。下文将逐一解析这些关键步骤的具体代码实现。

1. 编写请求拦截器

实现请求拦截器的基础逻辑较为直观,即在每次请求时自动附加Token以完成身份认证。

service.interceptors.request.use((config: InternalAxiosRequestConfig) => {  
    const userStore = useUserStore()  
    if (userStore.authInfo.accessToken && userStore.authInfo.accessToken !== "") {  
        // 设置头部 token  
        config.headers.Authorization = RequestConstant.Header.AuthorizationPrefix + userStore.authInfo.accessToken;  
    }  
    return config;  
},  (error: any) => {  
    return Promise.reject(error);  
    }  
);

当前方案在请求包含有效Token时,会将其附加至请求头发送至服务器。但在某些特定场景下,部分接口可能无需携带Token。为此,可在请求配置中通过config对象来判断是否需携带Token。示例如下:

request: (deptId: number, deptForm: DeptForm): AxiosPromise<void> => {  
    return request<void>({  
        url: DeptAPI.UPDATE.endpoint(deptId),  
        method: "put",  
        data: deptForm,
        headers: { 
            // 根据需要添加Token,或者通过自定义逻辑决定是否包含Authorization字段 
            token: false
        }
    });  
}

因此,在请求拦截器中需增加判断逻辑,以确认当前请求头中的Token是否必须携带。

// 代码省略

2. 深究响应拦截器

双Token刷新机制的难点在于响应拦截器的处理,因为后端会在特定情况下返回Token过期的信息。我们需要首先明确后端接口的响应数据结构。

2.1 接口介绍

  • 正常接口响应内容
// Status Code: 200 OK
{
    "code":"0000",
    "msg":"操作成功",
    "data":{}
}
  • accessToken 过期响应内容
// Status Code: 401 Unauthorized
{
    "code":"I009",
    "msg":"登录令牌过期"
}
  • accessToken 刷新响应内容
// Status Code: 200 OK
{
    "code": "0000",
    "msg": "操作成功",
    "data": {
        "accessToken": "",
        "refreshToken": "",
        "expires": ""
    }
}
  • refreshToken 过期响应内容
// Status Code: 200 OK
{
    "code": "I009",
    "msg": "登录令牌过期"
}

注意:当Status Code不为200时,Axios的响应拦截器会自动进入error方法。在此处,我们可以捕获HTTP状态码为401的请求,初步判断请求因Unauthorized(未授权)引发。然而,触发401状态码的原因多样,并不一定代表Token过期。因此,为准确判断Token是否真正过期,需进一步检查响应体中的code字段。

2.2 响应拦截器编写

基于上述接口定义,编写逻辑相对简单:判断error.response?.status === 401且code === I009,若满足条件则直接触发Token刷新。

service.interceptors.response.use(async (response: AxiosResponse) => {
        // 正常请求代码忽略
        return Promise.reject(new Error(msg || "Error"));
    },
    async (error: any) => {
        const userStore = useUserStore()
        if (error.response?.status === 401) {
            if (error.response?.data?.code === RequestConstant.Code.AUTH_TOKEN_EXPIRED) {
                // token 过期处理
                // 1. 刷新 token
                const loginResult: LoginResult = await userStore.refreshToken()
                if (loginResult) {
                    // refreshToken 未过期
                    // 2.1 重构请求头
                    error.config.headers.Authorization = RequestConstant.Header.AuthorizationPrefix + userStore.authInfo.accessToken;
                    // 2.2 请求
                    return await service.request(error.config);
                } else {
                    // refreshToken 过期
                    // 1. 重置登录 token , 跳转登录页
                    await userStore.resetToken()
                }
            } else {
                //  如果是系统发出的401 , 重置登录 token , 跳转登录页
                await userStore.resetToken()
            }
        } else if (error.response?.status === 403) {
           // 403 结果处理 , 代码省略
        } else {
           // 其他错误结果处理 , 代码省略
        }
        return Promise.reject(error.message);
    }
);

2.3 解决重复刷新问题

完成上述逻辑后,需考虑多个请求同时遭遇Token过期的情况。若缺乏适当控制机制,这些请求可能同时发起刷新操作,导致重复请求,甚至触发后端安全机制将其标记为危险操作。

为解决此问题,我们实现了一个单例Promise的刷新逻辑,通过singletonRefreshToken变量确保同一时间仅有一个请求发起Token刷新操作。其核心思想是让所有需刷新的请求共享同一个Promise,即使多个请求同时遇到Token过期,它们也仅等待同一刷新操作的结果,从而避免多次刷新。

/**
 * 刷新 token
 */
refreshToken(): Promise<LoginResult> {
    // 如果 singletonRefreshToken 不为 null 说明已经在刷新中,直接返回
    if (singletonRefreshToken !== null) {
        return singletonRefreshToken
    }
    // 设置 singletonRefreshToken 为一个 Promise 对象 , 处理刷新 token 请求
    singletonRefreshToken = new Promise<LoginResult>(async (resolve) => {
        await AuthAPI.REFRESH.request({
            accessToken: this.authInfo.accessToken as string,
            refreshToken: this.authInfo.refreshToken as string
        }).then(({data}) => {
            // 设置刷新后的Token
            this.authInfo = data
            // 刷新路由
            resolve(data)
        }).catch(() => {
            this.resetToken()
        })
    })
    // 最终将 singletonRefreshToken 设置为 null, 防止 singletonRefreshToken 一直占用
    singletonRefreshToken.finally(() => {
        singletonRefreshToken = null;
    })
    return singletonRefreshToken
}

重要点解析:

  • singletonRefreshToken 的使用:

    • singletonRefreshToken 是一个全局变量,用于保存当前正在进行的刷新操作。若某请求发现singletonRefreshToken不为null,则表明另一请求已发起刷新,该请求只需等待此操作完成,无需自行发起新刷新请求。
  • 共享同一个Promise:

    • 当singletonRefreshToken被赋值为新的Promise时,所有遭遇Token过期的请求均返回此Promise并等待其结果。此举有效避免了同时发起多个刷新请求。
  • 刷新完成后的处理:

    • 刷新操作完成后(无论成功与否),均通过finally将singletonRefreshToken置为null,从而确保下一次Token过期时能重新发起刷新请求。

通过该机制,可有效避免Token重复刷新问题,同时防止因过多重复请求引发的后端安全风险。该方法不仅提升了系统稳定性,还优化了资源使用,确保用户请求得到正确处理。

四、测试

  • 当我们携带过期Token访问接口时,后端将返回401状态码及I009错误码。

一、引言

此时进入响应拦截器的错误处理分支。

const loginResult: LoginResult = await userStore.refreshToken()
  • 携带之前过期的accessToken和未过期的refreshToken进行刷新。
  • 携带过期的accessToken的原因 :
    • 防止未过期的 accessToken 进行刷新
    • 防止 accessToken 和 refreshToken 不是同一用户发出的
    • 其他安全性考虑

一、引言

  • 获取到正常结果

一、引言

五、源码

前端源码位置 : yf/ yf-vue-admin / src / utils / request.ts

后端源码位置 : yf/ .. / impl / AuthServiceImpl.java

以上就是利用Axios实现无感知双Token刷新的详细教程的详细内容,更多关于Axios无感知Token刷新的资料请关注本站其它相关文章!

作者最新文章