Vue3中JWT认证:登录获取Token与自动刷新实现

一、JWT认证概述

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。其核心优势在于无状态性,服务器不需要存储会话信息,仅通过验证Token即可确认用户身份。典型应用场景包括API认证、单点登录等。

一个标准的JWT由三部分组成:Header(头部)、Payload(载荷)、Signature(签名),通过Base64URL编码后以点号分隔。在Vue3项目中,JWT通常由后端接口返回,前端负责存储、携带和刷新。

二、Vue3实现登录获取Token

1. 登录接口设计

前端通过表单提交用户名密码至认证接口,后端验证成功后返回JWT。响应数据结构建议如下:

  1. {
  2. "code": 200,
  3. "data": {
  4. "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  5. "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  6. "expiresIn": 3600
  7. }
  8. }

其中accessToken用于API请求认证,refreshToken用于获取新Token,expiresIn表示有效时间(秒)。

2. 登录组件实现

使用Vue3的Composition API构建登录表单:

  1. <template>
  2. <form @submit.prevent="handleSubmit">
  3. <input v-model="form.username" placeholder="用户名" />
  4. <input v-model="form.password" type="password" placeholder="密码" />
  5. <button type="submit">登录</button>
  6. </form>
  7. </template>
  8. <script setup>
  9. import { ref } from 'vue';
  10. import { useRouter } from 'vue-router';
  11. import { login } from '@/api/auth';
  12. import { setTokens } from '@/utils/auth';
  13. const router = useRouter();
  14. const form = ref({ username: '', password: '' });
  15. const handleSubmit = async () => {
  16. try {
  17. const res = await login(form.value);
  18. setTokens(res.data);
  19. router.push('/dashboard');
  20. } catch (error) {
  21. console.error('登录失败:', error);
  22. }
  23. };
  24. </script>

三、Token存储与管理

1. 存储方案选择

  • localStorage:持久化存储,适合长期保存Token
  • sessionStorage:页面关闭即清除,适合敏感操作
  • 内存存储:仅当前组件有效,安全性最高

推荐组合方案:

  1. // utils/auth.js
  2. export const setTokens = ({ accessToken, refreshToken }) => {
  3. localStorage.setItem('accessToken', accessToken);
  4. localStorage.setItem('refreshToken', refreshToken);
  5. // 可选:内存中保存过期时间
  6. sessionStorage.setItem('tokenExpire', Date.now() + 3600 * 1000);
  7. };
  8. export const getAccessToken = () => localStorage.getItem('accessToken');

2. 安全注意事项

  • 避免XSS攻击:使用securehttpOnly标志(需配合cookie)
  • 敏感操作使用短期Token
  • 定期清理过期Token

四、自动刷新Token实现

1. 刷新机制设计

accessToken过期时,使用refreshToken获取新Token。典型流程:

  1. 请求拦截器检测Token过期
  2. 调用刷新接口获取新Token
  3. 重新发起原始请求

2. 具体实现

创建Token服务类

  1. // services/tokenService.js
  2. import { getRefreshToken } from '@/utils/auth';
  3. import { refreshToken } from '@/api/auth';
  4. let isRefreshing = false;
  5. let subscribers = [];
  6. export const subscribeTokenRefresh = (callback) => {
  7. subscribers.push(callback);
  8. };
  9. export const notifySubscribers = (newToken) => {
  10. subscribers.forEach(cb => cb(newToken));
  11. subscribers = [];
  12. };
  13. export const getNewToken = async () => {
  14. if (isRefreshing) {
  15. return new Promise(resolve => {
  16. subscribeTokenRefresh((token) => resolve(token));
  17. });
  18. }
  19. isRefreshing = true;
  20. try {
  21. const res = await refreshToken({
  22. refreshToken: getRefreshToken()
  23. });
  24. // 更新存储的Token
  25. setTokens(res.data);
  26. notifySubscribers(res.data.accessToken);
  27. return res.data.accessToken;
  28. } finally {
  29. isRefreshing = false;
  30. }
  31. };

请求拦截器实现

  1. // utils/request.js
  2. import axios from 'axios';
  3. import { getAccessToken, clearTokens } from './auth';
  4. import { getNewToken } from '@/services/tokenService';
  5. const service = axios.create({
  6. baseURL: process.env.VUE_APP_API_BASE_URL,
  7. timeout: 5000
  8. });
  9. // 请求拦截器
  10. service.interceptors.request.use(
  11. async (config) => {
  12. const token = getAccessToken();
  13. if (token) {
  14. config.headers.Authorization = `Bearer ${token}`;
  15. }
  16. return config;
  17. },
  18. (error) => Promise.reject(error)
  19. );
  20. // 响应拦截器
  21. service.interceptors.response.use(
  22. (response) => response,
  23. async (error) => {
  24. const { response } = error;
  25. if (response?.status === 401) {
  26. try {
  27. const newToken = await getNewToken();
  28. // 重新发起原始请求
  29. error.config.headers.Authorization = `Bearer ${newToken}`;
  30. return service(error.config);
  31. } catch (refreshError) {
  32. clearTokens();
  33. window.location.href = '/login';
  34. return Promise.reject(refreshError);
  35. }
  36. }
  37. return Promise.reject(error);
  38. }
  39. );
  40. export default service;

五、最佳实践与优化

1. 性能优化

  • 刷新Token时使用请求队列,避免并发刷新
  • 设置合理的Token过期时间(建议accessToken 1小时,refreshToken 7天)
  • 使用指数退避策略处理刷新失败

2. 错误处理

  • 捕获Token无效(401)和刷新失败(403)
  • 提供友好的用户提示
  • 实现自动登出机制

3. 测试建议

  • 模拟Token过期场景
  • 测试并发请求下的刷新行为
  • 验证边界条件(如网络中断)

六、完整流程示例

  1. 用户登录 → 存储accessToken/refreshToken
  2. 发起API请求 → 携带accessToken
  3. Token过期 → 拦截器触发刷新
  4. 获取新Token → 重试原始请求
  5. 刷新失败 → 跳转登录页

通过这种设计,Vue3应用可以实现无缝的JWT认证体验,同时保证安全性。实际项目中可根据需求调整刷新策略,如使用滑动会话(Sliding Session)机制延长用户会话。

七、总结

本文详细介绍了Vue3中实现JWT认证的完整方案,包括Token获取、存储、自动刷新等关键环节。通过合理的架构设计和错误处理,可以构建出健壮的前端认证体系。实际开发中建议结合具体业务场景进行调整,并始终将安全性放在首位。