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,哪怕本地判断没过期,实际请求还是会失败
更优的替代方案
推荐用「本地预校验 + 接口响应统一拦截」的组合方案:
- 把全局校验逻辑放到
AuthProvider的useEffect中,定时(比如每1分钟)校验一次token有效期,而不是每次调用钩子都校验 - 封装全局fetch/axios拦截器,所有接口请求前先本地校验token是否过期,过期就先执行刷新再发请求;如果接口返回401状态码,统一触发登出/刷新逻辑,处理后端吊销token的情况
- 刷新token时加锁,避免同时触发多个刷新请求
内容的提问来源于stack exchange,提问作者ahgarawani
相关产品推荐
相关产品推荐

