当前位置: 首页 > news >正文

双Token实战:从无感刷新到安全防护,完整流程+代码解析

你是否常被这些问题困扰?

  • 用户吐槽“刚登录就过期,反复输密码太麻烦”;
  • 担心“Token存在localStorage,被XSS偷了怎么办”;
  • 调试时遇到“多个接口同时401,重复刷新Token乱成一锅粥”。

别担心,双Token机制(Access Token + Refresh Token) 正是为解决这些痛点而生。今天结合 Vue3/React + Axios 实战,带你从“原理→流程→代码→避坑”,搭建一套“登录一次,无感续期”的身份认证体系,兼顾安全与体验。

一、先搞懂:为什么网页端必须用双Token?

单Token方案的“两难困境”,是双Token存在的核心原因:

单Token痛点双Token解决方案
有效期短→用户频繁登录Access Token(短期2小时)+ Refresh Token(长期7天)
有效期长→泄露风险高Access Token仅存内存,Refresh Token安全存HttpOnly Cookie
无法兼顾“体验”与“安全”日常用Access Token,续期用Refresh Token,各司其职

双Token的核心分工,一句话讲透:

Access Token是“临时门禁卡”:用于接口请求,过期快、风险低;
Refresh Token是“长期通行证”:仅用于刷新门禁卡,安全存储、不直接参与接口访问。

二、网页端双Token完整流程:从请求到无感刷新

先看一张“闭环流程图”,明白整个机制的核心逻辑:

  1. 发起请求:前端接口自动携带Access Token;
  2. 401拦截:Access Token过期,后端返回401;
  3. 状态判断:若未在刷新中(isRefreshing=false),锁定刷新流程;
  4. 刷新Token:用Refresh Token调用刷新接口,获取新Access Token;
  5. 重试请求:用新Token重试当前失败的请求;
  6. 队列处理:期间其他401请求加入队列,统一用新Token重试;
  7. 状态重置:清空队列、解锁刷新,无感续期完成;
  8. 彻底过期:若Refresh Token无效,跳转登录页。

三、实战:Vue3 + Axios 实现双Token(附完整代码)

以 Vue3 为例(React 仅需替换状态管理库,逻辑完全一致),分4步落地。

1. 第一步:登录获取双Token,安全存储

用户登录后,后端返回 accessTokenrefreshToken,关键是区分存储方式,从源头防漏洞。

代码实现(登录逻辑:src/api/auth.js)
import axios from 'axios';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';// 登录函数
export async function login(username, password) {const store = useStore();const router = useRouter();try {// 1. 调用后端登录接口const res = await axios.post('/api/auth/login', { username, password });const { accessToken, refreshToken } = res.data;// 2. Access Token存Vuex(内存):刷新页面消失,防XSSstore.commit('auth/setAccessToken', accessToken);// 3. Refresh Token存HttpOnly Cookie:前端无法读取,安全等级最高// 生产环境必须加 Secure(仅HTTPS)、SameSite(防CSRF)document.cookie = `refreshToken=${refreshToken}; HttpOnly; Secure=${process.env.NODE_ENV === 'production'}; SameSite=Strict; path=/; max-age=${7 * 24 * 60 * 60}`; // 7天有效期// 4. 跳转首页router.push('/home');} catch (err) {alert(err.response?.data?.msg || '登录失败,请检查账号密码');}
}
存储安全重点:
  • ❌ 禁止把Access Token存localStorage/sessionStorage:易被XSS脚本窃取;
  • ✅ Refresh Token必须加 HttpOnly:前端JS无法访问,彻底杜绝XSS窃取风险;
  • ✅ 生产环境加 Secure:仅通过HTTPS传输Cookie,防止中途被拦截。

2. 第二步:请求拦截,自动携带Access Token

用Axios请求拦截器,给所有需要认证的接口“自动贴Token”,不用手动写请求头。

代码实现(Axios封装:src/utils/request.js)
import axios from 'axios';
import { useStore } from 'vuex';// 创建Axios实例
const request = axios.create({baseURL: '/api',timeout: 5000
});// 请求拦截器:添加Token
request.interceptors.request.use((config) => {const store = useStore();const accessToken = store.state.auth.accessToken;// 仅给“非 auth 接口”加Token(排除登录、刷新接口)if (accessToken && !config.url.includes('/api/auth/')) {config.headers.Authorization = `Bearer ${accessToken}`; // 符合HTTP认证规范}// 静态资源(图片、CSS)跳过Token(优化性能)if (config.url.match(/\.(png|jpg|jpeg|css|js)$/)) {delete config.headers.Authorization;}return config;},(error) => Promise.reject(error)
);export default request;

3. 第三步:响应拦截,实现无感刷新(核心)

当Access Token过期(后端返回401),用Refresh Token悄悄刷新,关键是解决“并发刷新”问题(多个接口同时401,避免重复调用刷新接口)。

代码实现(响应拦截器:src/utils/request.js 续)
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';// 全局变量:控制并发刷新
let isRefreshing = false; // 刷新锁:防止重复刷新
let requestQueue = [];    // 请求队列:存储等待刷新的请求// 响应拦截器:处理401
request.interceptors.response.use((response) => response,async (error) => {const originalRequest = error.config;const store = useStore();const router = useRouter();// 非401错误,直接抛出if (error.response?.status !== 401) {return Promise.reject(error);}// 已重试过的请求,避免无限循环if (originalRequest._retry) {return Promise.reject(error);}// 标记为已重试originalRequest._retry = true;try {// 场景1:正在刷新Token,当前请求加入队列if (isRefreshing) {return new Promise((resolve) => {requestQueue.push((newToken) => {// 刷新成功后,用新Token重试originalRequest.headers.Authorization = `Bearer ${newToken}`;resolve(request(originalRequest));});});}// 场景2:未在刷新,开始刷新流程isRefreshing = true; // 加锁store.commit('app/setLoading', true); // 显示全局加载(优化体验)// 1. 获取Refresh Token(前端无法读HttpOnly Cookie,需后端配合自动获取)// 实际项目中:后端直接从Cookie读refreshToken,前端无需传参const res = await axios.post('/api/auth/refresh');const newAccessToken = res.data.accessToken;// 2. 保存新Token到Vuexstore.commit('auth/setAccessToken', newAccessToken);// 3. 重试队列中所有请求requestQueue.forEach((callback) => callback(newAccessToken));requestQueue = []; // 清空队列// 4. 重试当前请求originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;return request(originalRequest);} catch (refreshError) {// 刷新失败(Refresh Token过期):彻底登出store.commit('auth/clearAccessToken'); // 清除Access Tokendocument.cookie = 'refreshToken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; // 清除Cookie// 携带当前路径,登录后返回原页面(优化体验)router.push(`/login?redirect=${encodeURIComponent(router.currentRoute.value.path)}`);return Promise.reject(refreshError);} finally {// 解锁 + 隐藏加载isRefreshing = false;store.commit('app/setLoading', false);}}
);
核心逻辑解析:
  • 并发控制isRefreshing 加锁,确保同一时间只有一个刷新请求;
  • 队列重试requestQueue 存储等待的请求,刷新成功后统一重试,用户无感知;
  • 后端配合:刷新接口无需前端传Refresh Token,后端直接从Cookie读取,更安全。

4. 第四步:登出清理,彻底失效Token

用户主动登出时,不仅要清除前端存储,还要通知后端让Refresh Token失效,防止被复用。

代码实现(登出逻辑:src/api/auth.js 续)
export async function logout() {const store = useStore();const router = useRouter();try {// 关键:调用后端登出接口,让Refresh Token在服务器端失效await axios.post('/api/auth/logout');} catch (err) {console.error('登出接口失败(不影响前端清理)', err);} finally {// 清除前端存储store.commit('auth/clearAccessToken');document.cookie = 'refreshToken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';// 跳转登录页router.push('/login');}
}

四、避坑指南:网页端双Token必注意的3个点

  1. 防XSS攻击

    • 始终将Refresh Token存 HttpOnly Cookie,前端无法读取;
    • Access Token存内存(Vuex/Redux),刷新页面后消失,即使被XSS窃取,有效期也只有2小时。
  2. 防CSRF攻击

    • Cookie加 SameSite=Strict:仅同域请求携带Cookie,防止跨站伪造;
    • 后端可额外加CSRF Token:在刷新接口中验证,双重防护。
  3. 提前刷新Token
    不要等401再刷新!解析Access Token的 exp 字段(如JWT),过期前30秒主动刷新,减少重试耗时:

    // 判断Token是否即将过期(剩余<30秒)
    export function isTokenExpiring(accessToken) {if (!accessToken) return true;const payload = JSON.parse(atob(accessToken.split('.')[1])); // 解析JWT payloadconst expTime = payload.exp * 1000; // 过期时间戳(秒转毫秒)return expTime - Date.now() < 30 * 1000;
    }// 在路由守卫中主动刷新
    router.beforeEach(async (to, from, next) => {const store = useStore();const accessToken = store.state.auth.accessToken;if (accessToken && isTokenExpiring(accessToken)) {await axios.post('/api/auth/refresh'); // 主动刷新}next();
    });
    

五、体验优化:让用户完全无感知

  1. 全局加载状态:刷新Token时显示遮罩,避免用户重复点击;
  2. 登录后返回原页面:通过 redirect 参数,登录后跳转回之前的页面;
  3. 静态资源跳过Token:图片、CSS等无需认证,减少请求头体积,优化性能。

六、总结:双Token的核心价值

双Token不是“复杂技术”,而是“体验与安全的平衡术”:

  • 对用户:一次登录,7天内无需重复输密码,体验流畅;
  • 对开发者:通过“内存+HttpOnly Cookie”存储、并发控制、提前刷新,筑牢安全防线;
  • 对系统:短期Access Token降低泄露风险,长期Refresh Token减少服务器压力。

如果你正在开发网页端单页应用(SPA),这套双Token方案绝对是身份认证的首选——按本文步骤实战,1小时就能搭建起“无感刷新+高安全”的认证体系!

http://www.dtcms.com/a/364002.html

相关文章:

  • 魔域服务器多少钱一个月?魔域服务器配置要求及推荐
  • Vue 3.5 重磅新特性:useTemplateRef 让模板引用更优雅、更高效!
  • 服务器托管需要注意什么事项?
  • 人工智能助力流感疫苗选择:MIT 团队推出 VaxSeer 系统
  • MySQL注意事项与规范
  • 开发AI编程工具的方案分析
  • SPI片选踩坑实录(硬件片选和软件片选)
  • Nacos配置文件攻防思路总结|揭秘Nacos被低估的攻击面|挖洞技巧
  • Python 基础核心概念与实战代码示例(含数据类型、变量、流程控制、数据结构、函数与文件操作)
  • # Shell 文本处理三剑客:awk、sed 与常用小工具详解
  • 如何修改 Docker 默认网段(网络地址池)配置:以使用 10.x.x.x 网段为例
  • 2024 年 AI 产业格局复盘:头部企业竞逐方向与中小玩家生存破局点
  • 跨境电商账号风控核心:IP纯净度与浏览器指纹的防护策略
  • 基于单片机车流车速检测系统设计
  • 90%的C++ 程序员都忽略了这个容器——unordered_multiset,让我们来看看开源项目中怎么使用的
  • 最小二乘法之线性回归篇(普通最小二乘OLS、加权最小二乘WLS、广义最小二乘GLS)-原理讲解
  • 毕业项目推荐:69-基于yolov8/yolov5/yolo11的轴承缺陷检测识别系统(Python+卷积神经网络)
  • Python入门教程之类型转换
  • 【 HarmonyOS 6 】HarmonyOS智能体开发实战:Function组件和智能体创建
  • 博客系统的测试
  • Shell脚本一键监控平台到期时间并钉钉告警推送指定人
  • 黑马头条面试重点业务
  • 如何避免研发文档命名混乱导致难以检索
  • 我们正在成为机械半类人你信吗?
  • Photoshop - Ps 处理图层
  • 数字社会学必读书目推荐!唐兴通20年数字社会学探索思想之旅再回顾人工智能社会学AI社会学下新秩序
  • 计算机保研机试准备——C++算法题(二)
  • 嵌入式学习 day62 SPI子系统、adxl345驱动、驱动回顾
  • 依托深兰科技AI技术生态,深兰教育携手沪上高校企业启动就业科创营
  • CRM数据暴风升级!3步将DataEase可视化神技嵌入Cordys,销售分析直接开挂!