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

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

在前后端分离的架构中,双 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 Token
        const refreshToken = localStorage.getItem('refreshToken');
        const response = await axios.post('https://your-api-url.com/refresh-token', { refreshToken });
        const { accessToken } = response.data;

        // 更新本地存储的 Access Token
        localStorage.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.kler.cn/a/583906.html

相关文章:

  • 实现图形界面访问无显示器服务器
  • Python网络爬虫之BeautifulSoup库的基本结构
  • Python :数据模型
  • 社交软件频繁更新,UI 设计在其中扮演什么角色?
  • ES 使用geo point 查询离目标地址最近的数据
  • Flutter 按钮组件 TextButton 详解
  • UFW 配置 Ubuntu 防火墙并设置防火墙规则
  • Spring Boot中引入Redis,以及RedisUtils完整工具类
  • 基于STM32F407ZGT6的硬件平台,(可选CubeMX) + PlatformIO软件开发的FreeRTOS部署指南
  • 什么是OF
  • 深入理解JavaScript构造函数与原型链:从原理到最佳实践
  • 《论语别裁》第01章 学而(24)五字串通五经
  • UE5与U3D引擎对比分析
  • hadoop第3课(hdfs shell)
  • 麒麟系统如何安装Anaconda
  • Day15:二叉树的后续遍历序列
  • C#中类‌的核心定义
  • 【存储中间件】Redis核心技术与实战(一):Redis入门与应用(常用数据结构:字符串String、哈希Hash、列表List)
  • LLM:了解大语言模型
  • OBS推WebRTC流,并添加毫秒级时间显示