欢迎光临
我们一直在努力

前端Vue实现双Token认证

在前端开发中,用户认证是保障系统安全的核心环节。传统的单Token认证方案往往面临“安全性”与“用户体验”难以兼顾的问题——Token过期后强制用户重新登录会影响体验,而延长Token有效期又会增加安全风险。

一、为什么需要双Token认证?先搞懂单Token的“痛点”

在了解双Token之前,我们先回顾下单Token认证的常见问题。通常我们会用JWT(JSON Web Token)作为认证凭证,流程是用户登录后后端返回一个Token,前端存储后每次请求都携带该Token。这种方案的核心问题集中在Token的有效期上:

  • 有效期短:为了安全,Token有效期通常设为1-2小时,过期后用户必须重新登录,频繁登录会让用户崩溃,尤其是在操作过程中突然过期,可能导致数据丢失。

  • 有效期长:如果延长Token有效期(比如7天),一旦Token被劫持,攻击者就能在长时间内非法访问系统,安全风险极高。

  • 刷新机制缺失:单Token没有优雅的刷新机制,无法在不打扰用户的情况下完成凭证更新。

而双Token认证通过引入“访问令牌(Access Token)”和“刷新令牌(Refresh Token)”两种Token,恰好解决了这些痛点,实现了“安全”与“体验”的平衡。

二、双Token认证的核心原理:两种Token各司其职

双Token方案的核心思想是“分工协作”,让两个Token承担不同的角色,具体如下:

1. 两个Token的定义与职责

Token类型

有效期

核心职责

安全级别

Access Token(访问令牌)

短期(如30分钟)

用于接口请求的直接认证凭证,证明用户当前有权访问资源

较低(过期快,即使泄露风险小)

Refresh Token(刷新令牌)

长期(如7天)

仅用于在Access Token过期时,请求获取新的Access Token

较高(权限单一,存储需更安全)

2. 双Token的完整认证流程

整个流程可以分为“登录获取Token”“接口请求认证”“Token刷新”三个核心阶段,用一张流程图概括如下:

从流程可以看出,Refresh Token相当于“令牌的令牌”,只在Access Token过期时才会使用,既避免了频繁登录,又降低了Token泄露的风险。

三、Vue项目中实现双Token认证:从0到1的实操

接下来我们结合Vue 3 + Pinia(状态管理)+ Axios(请求库),一步步实现双Token认证。核心步骤包括:登录获取Token、存储Token、请求拦截携带Token、响应拦截处理Token过期、退出清除Token。

1. 环境准备:安装依赖

首先确保项目中已安装所需依赖,若未安装可执行以下命令:

npm install axios pinia # 或 yarn add axios pinia

2. 第一步:封装Axios实例,处理拦截逻辑

Axios的请求拦截器用于携带Access Token,响应拦截器用于处理Token过期后的刷新逻辑。创建src/utils/request.js文件:

import axios from 'axios';
import { useUserStore } from '@/stores/userStore';
import router from '@/router';

// 创建Axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量中的接口地址
timeout: 5000
});

// 用于防止并发请求时重复刷新Token
let isRefreshing = false;
// 存储等待刷新Token的请求队列
let requestQueue = [];

// 请求拦截器:携带Access Token
service.interceptors.request.use(
(config) => {
const userStore = useUserStore();
// 如果有Access Token,添加到请求头
if (userStore.accessToken) {
config.headers.Authorization = `Bearer ${userStore.accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);

// 响应拦截器:处理Token过期、刷新逻辑
service.interceptors.response.use(
(response) => response.data, // 直接返回响应数据
async (error) => {
const userStore = useUserStore();
const originalRequest = error.config;

// 排除重复请求(避免刷新Token时再次进入拦截器)
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;

// 如果正在刷新Token,将当前请求加入队列
if (isRefreshing) {
return new Promise((resolve) => {
requestQueue.push(() => {
resolve(service(originalRequest));
});
});
}

isRefreshing = true;

try {
// 1. 用Refresh Token请求新的Access Token
const { accessToken } = await service.post('/auth/refresh', {
refreshToken: userStore.refreshToken
});

// 2. 更新Pinia中的Access Token
userStore.setAccessToken(accessToken);

// 3. 重新发起队列中的请求
requestQueue.forEach(cb => cb());
requestQueue = [];

// 4. 重新发起当前请求
return service(originalRequest);
} catch (refreshError) {
// 刷新Token失败(如Refresh Token过期),清除Token并跳登录页
userStore.logout();
router.push('/login?redirect=' + router.currentRoute.value.fullPath);
return Promise.reject(refreshError);
} finally {
// 结束刷新状态
isRefreshing = false;
}
}

// 非401错误直接返回
return Promise.reject(error);
}
);

export default service;

3. 第二步:用Pinia管理Token状态

创建Pinia仓库存储Token,避免在组件中直接操作localStorage(状态管理更集中,便于维护)。创建src/stores/userStore.js:

import { defineStore } from 'pinia';

// 定义用户状态仓库
export const useUserStore = defineStore('user', {
state: () => ({
// 从localStorage初始化Token(页面刷新后不丢失)
accessToken: localStorage.getItem('accessToken') || '',
refreshToken: localStorage.getItem('refreshToken') || ''
}),
actions: {
// 存储Token(同时存到localStorage和Pinia)
setTokens(accessToken, refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
},
// 单独更新Access Token
setAccessToken(accessToken) {
this.accessToken = accessToken;
localStorage.setItem('accessToken', accessToken);
},
// 退出登录:清除Token
logout() {
this.accessToken = '';
this.refreshToken = '';
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
},
// 判断是否登录(有Token则认为已登录)
isLogin() {
return !!this.accessToken;
}
}
});

为什么要同时用Pinia和localStorage?Pinia中的状态在页面刷新后会丢失,localStorage用于持久化存储;而Pinia便于组件间共享状态和触发更新,两者结合兼顾“持久化”和“易维护”。

四、双Token认证的安全优化:这些细节不能忘

双Token方案虽然比单Token更安全,但仍需注意以下细节,避免安全漏洞:

1. Refresh Token的存储安全

Refresh Token有效期长,一旦泄露风险更高,建议:

  • 前端存储:优先使用httpOnly Cookie存储Refresh Token(需后端配合),避免XSS攻击窃取;若用localStorage,需确保项目无XSS漏洞(如过滤用户输入、使用Vue的v-text避免innerHTML注入)。

  • 后端校验:Refresh Token应与用户账号、设备绑定,若检测到异常设备登录,直接失效该Refresh Token。

2. 接口请求的HTTPS加密

无论是Access Token还是Refresh Token,在网络传输过程中都需要加密,因此所有接口必须使用HTTPS协议,防止中间人攻击窃取Token。

3. Token的过期与失效机制

  • 后端应维护Refresh Token的黑名单,用户退出登录后,即使Refresh Token未过期,也应加入黑名单禁止使用。

  • Access Token和Refresh Token的有效期应根据业务场景合理设置(如管理系统Access Token设15分钟,普通应用设30分钟)。

4. 防止重复刷新Token

在Axios拦截器中,我们用isRefreshing标志和requestQueue队列解决了并发请求时重复刷新Token的问题,这是避免后端生成多个无效Token的关键。

五、总结:双Token认证的核心价值

Vue项目中的双Token认证,本质上是通过“短期访问凭证+长期刷新凭证”的组合,在“安全”与“体验”之间找到了最佳平衡点:

  • 对用户:Access Token过期时自动刷新,无需重复登录,体验更流畅。

  • 对系统:Access Token短期有效,即使泄露风险也极低;Refresh Token权限单一,仅用于刷新,降低了安全隐患。

最后,双Token认证并非银弹,仍需结合HTTPS、防XSS、接口权限校验等多种安全措施,才能构建真正可靠的用户认证体系。

赞(0)
未经允许不得转载:171主机测评 » 前端Vue实现双Token认证
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址