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显示未登录

相关推荐
Knight_AL4 天前
JWT 无状态认证深度解析:原理、优势
java·jwt
努力发光的程序员5 天前
互联网大厂Java面试场景:微服务与Spring Cloud技术点解析
spring cloud·grafana·prometheus·微服务架构·jwt·api网关·jaeger
我叫张小白。5 天前
Spring Boot拦截器详解:实现统一的JWT认证
java·spring boot·web·jwt·拦截器·interceptor
佛祖让我来巡山6 天前
小明的登录认证鉴权技术漫谈
jwt·token·cookie·session
mooyuan天天17 天前
CTFHub Web进阶-Json Web Token通关2:敏感信息泄露
jwt·ctfhub·json web token
星辰h1 个月前
基于JWT的RESTful登录系统实现
前端·spring boot·后端·mysql·restful·jwt
止观止1 个月前
JSON Web Token (JWT) 全面解析:原理、优缺点与最佳实践
jwt·1024程序员节·authz·authn
荣淘淘1 个月前
互联网大厂Java求职面试全景实战解析(涵盖Spring Boot、微服务及云原生技术)
java·spring boot·redis·jwt·cloud native·microservices·interview
ccccczy_2 个月前
Spring Security 深度解读:JWT 无状态认证与权限控制实现细节
java·spring security·jwt·authentication·authorization·securityfilterchain·onceperrequestfilter