基于 Axios 封装一个完美的双 token 无感刷新

用户登录之后,会返回一个用户的标识,之后带上这个标识请求别的接口,就能识别出该用户。

标识登录状态的方案有两种: session 和 jwt。

session 是通过 cookie 返回一个 id,关联服务端内存里保存的 session 对象,请求时服务端取出 cookie 里 id 对应的 session 对象,就可以拿到用户信息。

图片

jwt 不在服务端存储,会直接把用户信息放到 token 里返回,每次请求带上这个 token,服务端就能从中取出用户信息。

图片

这个 token 一般是放在一个叫 authorization 的 header 里。

这两种方案一个服务端存储,通过 cookie 携带标识,一个在客户端存储,通过 header 携带标识。

session 的方案默认不支持分布式,因为是保存在一台服务器的内存的,另一台服务器没有。

图片

jwt 的方案天然支持分布式,因为信息保存在 token 里,只要从中取出来就行。

图片

所以 jwt 的方案用的还是很多的。

服务端把用户信息放入 token 里,设置一个过期时间,客户端请求的时候通过 authorization 的 header 携带 token,服务端验证通过,就可以从中取到用户信息。

但是这样有个问题:

token 是有过期时间的,比如 3 天,那过期后再访问就需要重新登录了。

这样体验并不好。

想想你在用某个 app 的时候,用着用着突然跳到登录页了,告诉你需要重新登录了。

是不是体验很差?

所以要加上续签机制,也就是延长 token 过期时间。

主流的方案是通过双 token,一个 access_token、一个 refresh_token。

登录成功之后,返回这两个 token:

图片

访问接口时带上 access_token 访问:

图片

当 access_token 过期时,通过 refresh_token 来刷新,拿到新的 access_token 和 refresh_token

图片

这里的 access_token 就是我们之前的 token。

为什么多了个 refresh_token 就能简化呢?

因为如果你重新登录,是不是需要再填一遍用户名密码?而有了 refresh_token 之后,只要带上这个 token 就能标识用户,不需要传用户名密码就能拿到新 token。

而 access_token 一般过期时间设置的比较短,比如 30 分钟,refresh_token 设置的过期时间比较长,比如 7 天。

这样,只要你 7 天内访问一次,就能刷新 token,再续 7 天,一直不需要登录。

但如果你超过 7 天没访问,那 refresh_token 也过期了,就需要重新登录了。

想想你常用的 APP,是不是没再重新登录过?

而不常用的 APP,再次打开是不是就又要重新登录了?

这种一般都是双 token 做的。

知道了什么是双 token,以及它解决的问题,我们来实现一下。

新建个 nest 项目:

 npx nest new token-test

图片

进入项目,把它跑起来:

npm run start:dev

访问 http://localhost:3000 可以看到 hello world,代表服务跑成功了:

图片

在 AppController 添加一个 login 的 post 接口:

图片

@Post('login')
login(@Body() userDto: UserDto) {
    console.log(userDto);
    return 'success';
}

这里通过 @Body 取出请求体的内容,设置到 dto 中。

dto 是 data transfer object,数据传输对象,用来保存参数的。

我们创建 src/user.dto.ts

export class UserDto {
    username: string;
    password: string;
}

在 postman 里访问下这个接口:

图片

返回了 success,服务端也打印了收到的参数:

图片

然后我们实现下登录逻辑:

图片

这里我们就不连接数据库了,就是内置几个用户,匹配下信息。

const users = [
  { username: 'guang', password: '111111', email: 'xxx@xxx.com'},
  { username: 'dong', password: '222222', email: 'yyy@yyy.com'},
]

@Post('login')
login(@Body() userDto: UserDto) {
    const user = users.find(item => item.username === userDto.username);

    if(!user) {
      throw new BadRequestException('用户不存在');
    }

    if(user.password !== userDto.password) {
      throw new BadRequestException("密码错误");
    }

    return {
      userInfo: {
  
<think>嗯,用户想在Vue项目中配置多个代理,并且用Axios实现Token无感刷新。首先,我需要回忆一下Vue的代理配置。Vue CLI使用的是webpack的devServer.proxy,所以配置多个代理的话,应该在vue.config.js里设置不同的路径。比如,每个代理对应一个不同的API路径,这样前端请求不同的路径时会代理到不同的后端服务。 然后是关于Axios无感刷新Token。这通常涉及到在拦截器中处理401错误,当检测到Token过期时,自动发送刷新Token的请求,获取新的Token后重试原来的请求。同时,需要防止多个并发请求同时触发刷新,所以要用一个队列来保存等待中的请求,直到新的Token获取成功。 不过用户提到的是多个代理,可能需要为不同的API服务配置不同的Token刷新机制?或者所有的API都使用同一个Token体系?这里可能需要假设用户的不同代理对应同一个认证体系,所以Token刷新只需要处理一次。如果不同代理对应不同的认证服务,那可能需要更复杂的处理,但通常这种情况较少,先按同一认证处理。 接下来,参考用户提供的引用内容,比如引用2和引用4提到了代理配置,引用1、3、5讨论了Token刷新和Axios封装。需要结合这些信息给出具体步骤。 配置多个代理时,每个代理路径要明确,比如'/api1'和'/api2'分别代理到不同的目标服务器。在vue.config.js的devServer.proxy里设置多个条目。记得pathRewrite可能需要调整,确保路径正确。 Token无感刷新方面,Axios的请求拦截器用于携带Token,响应拦截器检查401错误。当检测到Token过期,发起刷新请求,使用refreshToken获取新的access token。同时,需要将后续的请求挂起,直到新Token到来,然后重新发送原来的请求。这里要注意防止多个刷新请求同时发生,可以用一个flag标记是否正在刷新,以及一个请求队列。 另外,用户可能需要处理refreshToken也过期的情况,这时候应该跳转到登录页面。在代码中,捕获刷新Token的失败,执行登出操作,清除Cookies并跳转。 在代码示例中,需要展示如何封装Axios实例,设置拦截器,以及处理并发请求。引用3中的代码展示了如何将Token添加到请求头,引用1的代码展示了刷新Token的具体实现,可以借鉴这些部分。 最后,生成相关问题时要围绕配置多个代理和Token刷新的优化、安全性、不同域处理等,可能的问题包括如何管理多个代理、如何处理多个Token、如何优化并发请求等。</think>### 分步实现Vue多代理配置与Token无感刷新 #### 一、配置多个代理 在`vue.config.js`中通过`devServer.proxy`配置多个代理路径,每个路径对应不同的后端服务: ```javascript // vue.config.js module.exports = { devServer: { proxy: { '/api1': { target: 'http://service1.example.com', changeOrigin: true, pathRewrite: { '^/api1': '' } }, '/api2': { target: 'http://service2.example.com', changeOrigin: true, pathRewrite: { '^/api2': '' } } } } } ``` #### 二、封装Axios实例 创建多个axios实例分别对应不同代理,并在请求拦截器中注入token: ```javascript // src/utils/request.js import axios from 'axios' import { getToken, getRefreshToken } from './auth' const createInstance = (baseURL) => { const instance = axios.create({ baseURL }) instance.interceptors.request.use(config => { const token = getToken() if (token) config.headers.Authorization = `Bearer ${token}` return config }) return instance } export const api1 = createInstance('/api1') export const api2 = createInstance('/api2') ``` #### 三、实现无感刷新机制 1. **响应拦截器检测401错误** 2. **维护刷新状态队列** 3. **刷新后重试请求** ```javascript let isRefreshing = false let requests = [] instance.interceptors.response.use( response => response, async error => { const { config, response } = error if (response?.status === 401 && !config._retry) { if (!isRefreshing) { isRefreshing = true try { const { token } = await refreshToken() setToken(token) // 存储新token requests.forEach(cb => cb(token)) requests = [] return instance(config) } catch (e) { logout() // 跳转登录页 } finally { isRefreshing = false } } return new Promise(resolve => { requests.push(newToken => { config.headers.Authorization = `Bearer ${newToken}` resolve(instance(config)) }) }) } return Promise.reject(error) } ) ``` #### 四、Token刷新函数 ```javascript // src/utils/auth.js export const refreshToken = async () => { const { data } = await axios.post('/auth/refresh', { refresh_token: getRefreshToken() }) return data } ``` ### 关键优化点 1. **并发控制**:通过`isRefreshing`标志位防止重复刷新 2. **队列机制**:使用请求队列存储待重试请求 3. **多实例隔离**:每个axios实例独立处理自己的token刷新 4. **错误边界**:刷新失败时清除本地凭证并跳转登录页[^3][^5]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值