React.js项目中如何在JWT令牌过期前调用refresh token接口
JWT令牌自动续期实现方案(React Hooks + Context API 版)
1. 核心逻辑说明
我们通过Auth Context统一维护令牌状态、续期定时器和相关操作方法,每次拿到新的JWT令牌后自动解析过期时间,提前1秒触发续期请求,全程不需要硬编码定时时长,适配服务端签发的令牌有效期规则。
2. 完整代码实现
2.1 封装Auth Context模块
// src/context/AuthContext.js import { createContext, useContext, useState, useEffect, useRef } from 'react'; const AuthContext = createContext(); // 简易JWT解码函数,无需引入第三方库 const parseJwt = (token) => { try { return JSON.parse(atob(token.split('.')[1])); } catch (e) { return null; } }; export const AuthProvider = ({ children }) => { const [accessToken, setAccessToken] = useState(localStorage.getItem('accessToken') || ''); const [refreshToken, setRefreshToken] = useState(localStorage.getItem('refreshToken') || ''); // 用ref存储定时器id,避免闭包问题和重复创建 const refreshTimerRef = useRef(null); // 清理已有续期定时器 const clearRefreshTimer = () => { if (refreshTimerRef.current) { clearTimeout(refreshTimerRef.current); refreshTimerRef.current = null; } }; // 调用续期接口逻辑 const refreshAccessToken = async () => { try { const res = await fetch('/api/refresh-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) }); if (!res.ok) throw new Error('续期失败'); const { accessToken: newAccessToken } = await res.json(); // 更新令牌后会自动触发新的定时器 setAccessToken(newAccessToken); localStorage.setItem('accessToken', newAccessToken); return newAccessToken; } catch (e) { // 续期失败直接登出,跳登录页 logout(); return null; } }; // 启动续期定时器 const startRefreshTimer = (token) => { clearRefreshTimer(); const payload = parseJwt(token); if (!payload?.exp) return; // 计算过期前1秒的触发延迟(exp为秒级时间戳,转毫秒计算) const expireTime = payload.exp * 1000; const delay = expireTime - Date.now() - 1000; // 如果当前已经临近过期,直接触发续期 if (delay <= 0) { refreshAccessToken(); return; } refreshTimerRef.current = setTimeout(refreshAccessToken, delay); }; // 登录方法,登录成功后启动定时器 const login = (tokens) => { setAccessToken(tokens.accessToken); setRefreshToken(tokens.refreshToken); localStorage.setItem('accessToken', tokens.accessToken); localStorage.setItem('refreshToken', tokens.refreshToken); startRefreshTimer(tokens.accessToken); }; // 登出方法,清理状态和定时器 const logout = () => { setAccessToken(''); setRefreshToken(''); localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); clearRefreshTimer(); // 这里可补充跳转登录页逻辑 }; // 页面初始化时,如果本地有有效令牌直接启动定时器 useEffect(() => { if (accessToken) { startRefreshTimer(accessToken); } // 组件卸载时清理定时器,避免内存泄漏 return () => clearRefreshTimer(); }, [accessToken]); return ( <AuthContext.Provider value={{ accessToken, login, logout, refreshAccessToken }}> {children} </AuthContext.Provider> ); }; // 自定义Hooks,方便业务组件调用鉴权能力 export const useAuth = () => useContext(AuthContext);
2.2 全局挂载Context
// src/index.js import { AuthProvider } from './context/AuthContext'; import App from './App'; ReactDOM.createRoot(document.getElementById('root')).render( <AuthProvider> <App /> </AuthProvider> );
3. 注意事项
- 不要硬编码59秒作为定时器延迟:必须通过JWT payload的exp字段动态计算,避免服务端签发令牌的时间和客户端时间存在偏差导致续期时机错误
- 续期接口需要加入全局请求拦截器白名单,不要给续期接口携带过期的accessToken鉴权,避免还没续期就被拦截
- 存在并发请求同时检测到令牌过期的场景时,可以在refreshAccessToken方法中加loading状态锁,避免同时发起多次续期请求
- 若refreshToken本身过期,接口会返回401错误,此时直接触发登出逻辑即可
内容的提问来源于stack exchange,提问作者Mehmet Ceylan
相关产品推荐
相关产品推荐

