双 Token 无感刷新机制在前后端分离架构中实现

devtools/2025/3/14 11:23:02/

在前后端分离的架构中,双 Token 无感刷新是一种常见的身份验证机制,用于在 Access Token 过期时,通过 Refresh Token 自动获取新的 Access Token,从而避免用户频繁登录。


1. 双 Token 无感刷新的核心流程

1.1 核心流程

  1. 用户登录

    • 前端发送用户名和密码到后端
    • 后端验证成功后,返回 Access TokenRefresh Token
    • 前端将这两个 Token 存储在本地(如 localStorageCookie)。
  2. Access Token 过期

    • 前端发起请求时,携带 Access Token
    • 如果 Access Token 过期,后端返回 401 Unauthorized 错误。
  3. 自动刷新 Token

    • 前端检测到 401 错误后,使用 Refresh Token 请求新的 Access Token
    • 后端验证 Refresh Token 的有效性,如果有效,返回新的 Access Token
    • 前端更新本地存储的 Access Token,并重试之前的请求。
  4. Refresh Token 过期

    • 如果 Refresh Token 也过期,后端返回 401 错误。
    • 前端清除本地存储的 Token,并跳转到登录页面。

2. 前端实现(React + TypeScript)

2.1 配置 Axios 拦截器

Axios 拦截器用于在请求发送前和响应返回后执行逻辑,是实现无感刷新的核心。

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';// 创建 Axios 实例
const api: AxiosInstance = axios.create({baseURL: 'https://your-api-url.com',headers: {'Content-Type': 'application/json',},
});// 请求拦截器:在请求头中添加 Access Token
api.interceptors.request.use((config: AxiosRequestConfig) => {const accessToken = localStorage.getItem('accessToken');if (accessToken) {config.headers.Authorization = `Bearer ${accessToken}`;}return config;},(error: AxiosError) => {return Promise.reject(error);}
);// 响应拦截器:处理 Access Token 过期
api.interceptors.response.use((response: AxiosResponse) => {return response;},async (error: AxiosError) => {const originalRequest = error.config;// 检测到 401 错误且未重试过if (error.response?.status === 401 && !originalRequest._retry) {originalRequest._retry = true; // 标记请求已重试try {// 使用 Refresh Token 获取新的 Access Tokenconst refreshToken = localStorage.getItem('refreshToken');const response = await axios.post('https://your-api-url.com/refresh-token', { refreshToken });const { accessToken } = response.data;// 更新本地存储的 Access TokenlocalStorage.setItem('accessToken', accessToken);api.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;// 重试原始请求return api(originalRequest);} catch (refreshError) {// 刷新 Token 失败,清除本地存储并跳转到登录页localStorage.removeItem('accessToken');localStorage.removeItem('refreshToken');window.location.href = '/login';return Promise.reject(refreshError);}}return Promise.reject(error);}
);export default api;

2.2 使用 React Context 管理 Token 状态

通过 React Context 管理 Token 的状态,方便在组件中共享和更新。

import React, { createContext, useContext, useState, useEffect } from 'react';interface AuthContextType {accessToken: string | null;refreshToken: string | null;setTokens: (accessToken: string, refreshToken: string) => void;clearTokens: () => void;
}const AuthContext = createContext<AuthContextType | undefined>(undefined);export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {const [accessToken, setAccessToken] = useState<string | null>(localStorage.getItem('accessToken'));const [refreshToken, setRefreshToken] = useState<string | null>(localStorage.getItem('refreshToken'));const setTokens = (accessToken: string, refreshToken: string) => {localStorage.setItem('accessToken', accessToken);localStorage.setItem('refreshToken', refreshToken);setAccessToken(accessToken);setRefreshToken(refreshToken);};const clearTokens = () => {localStorage.removeItem('accessToken');localStorage.removeItem('refreshToken');setAccessToken(null);setRefreshToken(null);};return (<AuthContext.Provider value={{ accessToken, refreshToken, setTokens, clearTokens }}>{children}</AuthContext.Provider>);
};export const useAuth = () => {const context = useContext(AuthContext);if (!context) {throw new Error('useAuth must be used within an AuthProvider');}return context;
};

2.3 在组件中使用

在需要身份验证的组件中,使用 useAuth Hook 获取和更新 Token。

import React from 'react';
import { useAuth } from './AuthContext';
import api from './api';const ProtectedComponent: React.FC = () => {const { accessToken, setTokens, clearTokens } = useAuth();const fetchData = async () => {try {const response = await api.get('/protected-resource');console.log(response.data);} catch (error) {console.error('Failed to fetch data', error);}};return (<div><button onClick={fetchData}>Fetch Data</button><button onClick={clearTokens}>Logout</button></div>);
};export default ProtectedComponent;

3. 后端实现(Node.js + Express)

3.1 登录接口

登录成功后返回 Access TokenRefresh Token

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();app.use(express.json());const SECRET_KEY = 'your-secret-key';
const REFRESH_SECRET_KEY = 'your-refresh-secret-key';app.post('/login', (req, res) => {const { username, password } = req.body;// 模拟用户验证if (username === 'admin' && password === 'password') {const accessToken = jwt.sign({ username }, SECRET_KEY, { expiresIn: '15m' });const refreshToken = jwt.sign({ username }, REFRESH_SECRET_KEY, { expiresIn: '7d' });res.json({ accessToken, refreshToken });} else {res.status(401).json({ message: 'Invalid credentials' });}
});

3.2 刷新 Token 接口

验证 Refresh Token 并返回新的 Access Token

app.post('/refresh-token', (req, res) => {const { refreshToken } = req.body;if (!refreshToken) {return res.status(401).json({ message: 'Refresh token is required' });}try {const decoded = jwt.verify(refreshToken, REFRESH_SECRET_KEY);const accessToken = jwt.sign({ username: decoded.username }, SECRET_KEY, { expiresIn: '15m' });res.json({ accessToken });} catch (error) {res.status(401).json({ message: 'Invalid refresh token' });}
});

4. 总结

4.1 核心优势

  • 无感刷新:用户无需手动登录即可获取新的 Access Token。
  • 安全性:Access Token 有效期短,Refresh Token 有效期长但仅用于刷新 Access Token。
  • 用户体验:减少用户因 Token 过期而需要频繁登录的情况。

4.2 注意事项

  • 并发请求:当多个请求同时触发 Token 刷新时,需要确保只有一个刷新请求被发送。
  • 安全性:Refresh Token 应存储在安全的地方(如 HttpOnly Cookie)。
  • 错误处理:在 Token 刷新失败时,应清除本地存储并跳转到登录页面。

通过以上实现,双 Token 无感刷新机制可以在前后端分离的项目中有效提升用户体验和安全性。


http://www.ppmy.cn/devtools/167009.html

相关文章

i18n的替换处理

i18n的替换处理 背景描述 在开发的过程中&#xff0c;需要对项目文件的中文使用i18n进行多语言处理。在特定的页面&#xff0c;中文会有特定的处理&#xff0c;例如: 存在5台设备&#xff0c;其中 5 是一个变量&#xff0c;同时具有特定的样式&#xff0c;那么这个时候应该如何…

网络空间安全(28)风险评估实施

前言 风险评估的实施是一个系统而细致的过程&#xff0c;旨在全面识别、评估潜在风险&#xff0c;并制定相应的风险管理策略。 一、确定评估范围和目标 评估范围&#xff1a; 明确评估的对象和范围&#xff0c;如特定的业务流程、项目、设备配置、信息系统等。确定评估的时间范…

深度学习笔记35_Inception v1算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、我的环境&#xff1a; 1.语言环境&#xff1a;Python 3.8 2.编译器&#xff1a;Pycharm 3.深度学习环境&#xff1a; torch1.12.1cu11…

ChatGPT、DeepSeek、Grok 三者对比:AI 语言模型的博弈与未来

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 随着人工智能技术的飞速发展&#xff0c;AI 语言模型已经成为人机交互、内容创作、代码生成、智能问答等领域的重要工具…

国产编辑器EverEdit - 工具栏自定义及认识工具栏上的按钮

1 设置-高级-工具条 1.1 设置说明 1.1.1 工具条自定义 选择主菜单工具 -> 设置 -> 常规&#xff0c;在弹出的选项窗口中选择工具条分类&#xff0c;如下图所示&#xff1a; 左侧窗口是当前支持所有功能按钮列表(上图中居中栏)&#xff0c;右侧的窗口是当前显示在工具栏…

oXygen XML Editor—— XML编辑与结构化内容管理的优秀工具

Oxygen XML Editor是用于XML创作和开发的重要工具&#xff0c;为所有用户&#xff08;从初学者到专家&#xff09;量身定制的&#xff0c;它是通用的、跨平台的&#xff0c;并且可以作为独立应用程序和Eclipse插件使用。凭借对XML技术的强大支持&#xff0c;它提供了易于创建、…

TightVNC服务端安装与配置:Windows远程桌面的内网穿透解决方案

文章目录 前言1. 安装TightVNC服务端2. 局域网VNC远程测试3. Win安装Cpolar工具4. 配置VNC远程地址5. VNC远程桌面连接6. 固定VNC远程地址7. 固定VNC地址测试 前言 在现代快节奏的工作和生活中&#xff0c;跨地理界限的高效协作变得越来越重要。无论是远程办公、技术支持还是家…

Milvus向量数据库操作的详细输入输出参数说明及示例

以下将详细介绍 Milvus 向量数据库中保存&#xff08;插入&#xff09;、删除、查询&#xff08;query 和 search&#xff09;操作的输入输出参数&#xff0c;并给出代码示例。 环境准备 确保已经安装 pymilvus 库&#xff0c;若未安装&#xff0c;可使用以下命令&#xff1a…