You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React应用中如何为现有Auth Context添加JWT过期校验与令牌刷新能力

方案实现步骤

1. 新增JWT过期校验工具函数

首先实现一个通用的JWT有效期校验方法,不用引入额外依赖,直接解析JWT payload中的exp字段判断:

// 可以放在utils.js或者直接写到context.js中
function isTokenExpired(token) {
  if (!token) return true;
  try {
    const payload = JSON.parse(atob(token.split('.')[1]));
    // exp是秒级时间戳,转成毫秒和当前时间比较
    return payload.exp * 1000 < Date.now();
  } catch (e) {
    // 解析失败说明token无效,直接判定为过期
    return true;
  }
}

2. 改造useAuthState钩子自动校验

在原有钩子逻辑中加入过期判断,检测到过期直接触发登出,或者触发刷新token逻辑:

// context.js修改后的useAuthState
import { logout } from './actions'; // 引入登出方法

export function useAuthState() {
  const context = React.useContext(AuthStateContext);
  const dispatch = useAuthDispatch(); // 内部引入dispatch用于触发状态更新

  if (context === undefined) {
    throw new Error("useAuthState must be used within a AuthProvider");
  }

  // 每次调用钩子都校验token有效期
  if (context.token && isTokenExpired(context.token)) {
    // 方式一:直接登出
    logout(dispatch);
    // 方式二:触发刷新token逻辑(需要先实现刷新方法,见下文)
    // refreshToken(dispatch);
  }

  return context;
}

3. (可选)新增刷新token逻辑

如果后端支持refresh_token机制,可以实现无感刷新,不需要用户重新登录:
首先修改reducers.js新增刷新相关的状态处理:

// reducers.js 新增case
export const AuthReducer = (initialState, action) => {
  switch (action.type) {
    // 原有case保留,新增以下内容
    case "REQUEST_REFRESH":
      return {
        ...initialState,
        loading: true,
      };
    case "REFRESH_SUCCESS":
      return {
        ...initialState,
        userDetails: action.payload.user,
        token: action.payload.token,
        loading: false,
        errorMessage: null,
      };
    case "REFRESH_ERROR":
      // 刷新失败直接登出
      localStorage.removeItem("currentUser");
      return {
        ...initialState,
        userDetails: "",
        token: "",
        loading: false,
        errorMessage: action.error,
      };
    // 原有default保留
  }
}

然后在actions.js中新增刷新token的异步方法:

// actions.js 新增
export async function refreshToken(dispatch) {
  const currentUser = JSON.parse(localStorage.getItem("currentUser"));
  if (!currentUser?.refreshToken) {
    logout(dispatch);
    return;
  }
  const requestOptions = {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refreshToken: currentUser.refreshToken }),
  };

  try {
    dispatch({ type: "REQUEST_REFRESH" });
    let response = await fetch(`${ROOT_URL}/auth/refresh`, requestOptions);
    let data = await response.json();

    if (data.user) {
      dispatch({ type: "REFRESH_SUCCESS", payload: data });
      localStorage.setItem("currentUser", JSON.stringify(data));
      return data;
    }

    dispatch({ type: "REFRESH_ERROR", error: data.errors[0] });
  } catch (error) {
    dispatch({ type: "REFRESH_ERROR", error: error });
  }
}

方案优劣分析

当前「每次调用useAuthState校验」的优点

  • 实现成本极低,无需修改其他业务逻辑,所有用到登录态的组件自动触发校验
  • 能提前拦截过期状态,避免用无效token发送业务请求

存在的问题

  • 频繁调用useAuthState会重复执行校验逻辑,虽然性能损耗很小,但如果同时触发刷新token会出现竞态问题,导致多次调用刷新接口
  • 本地校验不可靠:如果后端提前吊销了token,哪怕本地判断没过期,实际请求还是会失败

更优的替代方案

推荐用「本地预校验 + 接口响应统一拦截」的组合方案:

  1. 把全局校验逻辑放到AuthProvider的useEffect中,定时(比如每1分钟)校验一次token有效期,而不是每次调用钩子都校验
  2. 封装全局fetch/axios拦截器,所有接口请求前先本地校验token是否过期,过期就先执行刷新再发请求;如果接口返回401状态码,统一触发登出/刷新逻辑,处理后端吊销token的情况
  3. 刷新token时加锁,避免同时触发多个刷新请求

内容的提问来源于stack exchange,提问作者ahgarawani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 21:09:04