accesstoken和refreshtoken的理解

一个企业级完整的 React AuthProvider + 自动刷新 AccessToken + 过期退出 示例。

核心思路:

  1. AccessToken 有效期短(例如 30 分钟)

  2. RefreshToken 有效期长(例如 7 天)

  3. 前端 UI 状态与后端权限保持一致

  4. AccessToken 过期 → 自动刷新

  5. RefreshToken 过期 → 自动退出登录

下面是完整示例:

复制代码
import React, { createContext, useState, useEffect, useContext } from 'react';
import axios from 'axios';
import { login as loginApi, logout as logoutApi, refreshToken as refreshTokenApi } from '../api/auth';

export const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(null);
  const [loading, setLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  // 初始化:检查 token + refresh token
  useEffect(() => {
    const initAuth = async () => {
      const storedToken = localStorage.getItem('token');
      const storedRefreshToken = localStorage.getItem('refreshToken');
      const storedUser = localStorage.getItem('user');

      if (!storedToken || !storedRefreshToken || !storedUser) {
        setLoading(false);
        return;
      }

      setToken(storedToken);
      setUser(JSON.parse(storedUser));
      setIsAuthenticated(true);

      // 这里可以尝试刷新 token,如果已过期
      try {
        await tryRefreshToken();
      } catch (error) {
        logout();
      }

      setLoading(false);
    };

    initAuth();
  }, []);

  // 登录
  const login = async (credentials) => {
    const response = await loginApi(credentials);
    const { code, message, data } = response;

    if (code !== 200) {
      setIsAuthenticated(false);
      setUser(null);
      setToken(null);
      throw new Error(message || '登录失败');
    }

    const { token: accessToken, refreshToken, userType, userName } = data;
    const userData = { userType, userName };

    setToken(accessToken);
    setUser(userData);
    setIsAuthenticated(true);

    localStorage.setItem('token', accessToken);
    localStorage.setItem('refreshToken', refreshToken);
    localStorage.setItem('user', JSON.stringify(userData));
  };

  // 登出
  const logout = async () => {
    try {
      await logoutApi();
    } catch (error) {
      console.error('登出API失败', error);
    } finally {
      setToken(null);
      setUser(null);
      setIsAuthenticated(false);

      localStorage.removeItem('token');
      localStorage.removeItem('refreshToken');
      localStorage.removeItem('user');
    }
  };

  // 尝试刷新 token
  const tryRefreshToken = async () => {
    const storedRefreshToken = localStorage.getItem('refreshToken');
    if (!storedRefreshToken) throw new Error('没有 refresh token');

    try {
      const res = await refreshTokenApi({ refreshToken: storedRefreshToken });
      const { accessToken, refreshToken } = res.data;

      setToken(accessToken);
      localStorage.setItem('token', accessToken);
      localStorage.setItem('refreshToken', refreshToken);
    } catch (error) {
      throw new Error('Refresh token 已过期');
    }
  };

  // Axios 拦截器,自动刷新 token 或退出登录
  useEffect(() => {
    const interceptor = axios.interceptors.response.use(
      (res) => res,
      async (error) => {
        const originalRequest = error.config;

        if (error.response?.status === 401 && !originalRequest._retry) {
          originalRequest._retry = true;

          try {
            await tryRefreshToken();
            originalRequest.headers['Authorization'] = 'Bearer ' + localStorage.getItem('token');
            return axios(originalRequest);
          } catch (e) {
            logout();
            return Promise.reject(e);
          }
        }

        return Promise.reject(error);
      }
    );

    return () => {
      axios.interceptors.response.eject(interceptor);
    };
  }, []);

  const value = {
    user,
    token,
    loading,
    isAuthenticated,
    login,
    logout,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

export const useAuthStore = () => {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuthStore 必须在 AuthProvider 内使用');
  return context;
};

效果说明

  1. 页面刷新 → 尝试恢复 token,并刷新一次 AccessToken,如果 RefreshToken 有效

  2. AccessToken 过期 → 自动刷新 token,无需登出

  3. RefreshToken 过期 → 前端自动登出,并清理 localStorage

  4. 所有 axios 请求 401 → 自动刷新或登出


前端 RefreshToken 自动续签 + AccessToken 过期退出逻辑 的完整流程


JWT + RefreshToken 生命周期图

复制代码
用户登录
   │
   ▼
后端返回:
{
  accessToken (30分钟),
  refreshToken (7天)
}
   │
   ├─ 前端保存:
   │   localStorage.token = accessToken
   │   localStorage.refreshToken = refreshToken
   │   localStorage.user = userData
   ▼
前端正常访问接口 (带 AccessToken)
   │
   ▼
后端验证 AccessToken
   ├─ 有效 → 返回数据
   └─ 过期 → 返回 401
               │
               ▼
      前端 Axios 拦截器捕获 401
               │
               ▼
        尝试刷新 AccessToken (用 RefreshToken)
               │
        ┌──────┴────────┐
        │               │
 RefreshToken 有效      RefreshToken 过期
        │               │
        ▼               ▼
 后端返回新 AccessToken   前端登出
        │
        ▼
前端保存新 AccessToken
        │
        ▼
重试原请求 → 用户无感知

🔹 说明

  1. AccessToken(短期)

    • 用于每次接口访问

    • 过期快(30分钟)

    • 过期 → 自动刷新

  2. RefreshToken(长期)

    • 用于刷新 AccessToken

    • 过期慢(7天)

    • 过期 → 强制登出

  3. 前端状态逻辑

    • isAuthenticated = token存在 && token未过期

    • 页面刷新 → 尝试用 RefreshToken 刷新 AccessToken

    • RefreshToken 无效 → 自动登出

  4. 企业级特点

    • 用户体验:不会因为 AccessToken 过期而突然登出

    • 安全性:AccessToken 短期有效,盗用风险低

    • 全局统一:Axios 拦截器 + 后端校验 + 前端状态一致

情况 前端 UI 后端验证
AccessToken 有效 显示登录 接口正常返回数据
AccessToken 过期,RefreshToken 有效 自动刷新 token → 显示登录 返回新 AccessToken
AccessToken 过期,RefreshToken 过期 前端 logout → 显示未登录 401 Unauthorized

核心:AccessToken 到期不等于登出,只有 RefreshToken 到期才真正登出。


复制代码
[用户访问页面 / 刷新页面]
          │
          ▼
[AuthProvider useEffect 初始化]
          │
          ▼
  localStorage 检查 token / refreshToken / user
          │
   ┌──────┴──────┐
   │             │
 token / refreshToken 不存在 → logout → 显示未登录
   │
   ▼
 token / refreshToken 存在 → 尝试刷新 token
          │
   ┌──────┴──────┐
   │             │
刷新成功 → 更新 AccessToken → 保持登录状态
刷新失败 → logout → 显示未登录
          │
          ▼
[用户访问接口]
          │
          ▼
后端验证 AccessToken
   ┌─────────┴─────────┐
   │                   │
 AccessToken 有效 → 返回数据
 AccessToken 过期 → 返回 401
          │
          ▼
[Axios 拦截器捕获 401]
          │
   ┌──────┴──────┐
   │             │
尝试刷新 token      RefreshToken 已过期 → logout
   │
刷新成功 → 更新 AccessToken → 重试请求 → 用户无感知
刷新失败 → logout → 显示未登录

🔹 说明

  1. 前端缓存(localStorage)

    • 只是"假设登录状态",不保证有效

    • 主要作用:刷新页面时可以尝试恢复登录

  2. AccessToken(短期)

    • 用于每次接口访问

    • 过期 → 后端返回 401 → 前端自动刷新或登出

  3. RefreshToken(长期)

    • 用于刷新 AccessToken

    • 过期 → 必须登出,UI 才显示未登录

  4. 企业级特点

    • 用户体验好:AccessToken 过期不影响 UI

    • 安全性高:AccessToken 短期有效,盗用风险低

    • 前端状态与后端权限同步,保证不会假象登录

    前端 后端
    ─────────────── ───────────────

    用户登录 ───────────────▶ 验证账号密码


    返回 AccessToken(30m)
    返回 RefreshToken(7d)

    └─────────▶ 前端存 localStorage
    token + refreshToken + user

    ────────────────────────────────────────────────────────────

    用户访问接口 ─────────────▶ 后端验证 AccessToken
    ├─ AccessToken 有效 → 返回数据
    └─ AccessToken 过期 → 返回 401

    ────────────────────────────────────────────────────────────

    Axios 拦截器捕获 401 ─────────▶ 尝试刷新 AccessToken

    ┌────────────┴────────────┐
    │ │
    RefreshToken 有效 RefreshToken 过期
    │ │
    后端返回新 AccessToken 后端返回 401
    │ │
    前端更新 token → 重试原请求 前端 logout → 清理 localStorage
    │ │
    ▼ ▼
    用户无感知,继续操作 用户真正登出,UI显示未登录

相关推荐
清风徐来QCQ16 小时前
传统Session和JWT方案的区别
jwt·session
曲幽3 天前
FastAPI + SQLite:从基础CRUD到安全并发的实战指南
python·sqlite·fastapi·web·jwt·form·sqlalchemy·oauth2
課代表3 天前
大语言模型能够理解的11种文件格式
人工智能·语言模型·自然语言处理·llm·markdown·token·模型
三天不学习7 天前
如何高效且节省的使用Cursor?Token优化到Project Rules设计以及提示词的实用技巧
ai编程·token·cursor
利刃大大9 天前
【SpringBoot】validation参数校验 && JWT鉴权实现 && 加密/加盐
java·spring boot·jwt·加密
二进制_博客9 天前
JWT权限认证快速入门
java·开发语言·jwt
heartbeat..11 天前
Web 状态管理核心技术详解 + JWT 双 Token (Access/Refresh Token) 自动登录
java·网络·jwt·token
musk121216 天前
jdk21 + springboot 3.2.X + security + jwt
security·jwt
源代码•宸16 天前
goframe框架签到系统项目开发(用户认证、基于 JWT 实现认证、携带access token获取用户信息)
服务器·开发语言·网络·分布式·后端·golang·jwt
Joy T17 天前
【深度长文】大模型应用开发指南:从概率原理到对话系统架构解析
prompt·对话系统·token