React结合React Router:JWT令牌过期后如何自动登出用户?
结合React Router实现JWT会话自动过期登出的最佳方案
1. 解析并存储JWT过期时间
后端返回JWT后,除了把token存入localStorage,还要解析出payload里的exp字段(秒级时间戳),转成毫秒级后存储到期时间。这样不用每次都解码token判断过期状态。
// 解码JWT payload的工具函数 const decodeJwt = (token) => { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); return JSON.parse(window.atob(base64)); }; // 登录成功后存储token和过期时间 const handleLogin = async () => { const response = await fetch('/api/login', { method: 'POST', body: ... }); const { token } = await response.json(); const { exp } = decodeJwt(token); // 转成毫秒级时间戳,方便和Date.now()比较 const expiresAt = exp * 1000; localStorage.setItem('authToken', token); localStorage.setItem('expiresAt', expiresAt.toString()); };
2. 全局管理登录状态与自动登出逻辑
用React Context封装登录状态和登出方法,在全局监听会话过期,同时处理页面刷新、标签页切换的场景。
import { createContext, useContext, useEffect, useState } from 'react'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [isAuthenticated, setIsAuthenticated] = useState(false); let logoutTimer; // 初始化时检查登录状态 useEffect(() => { const token = localStorage.getItem('authToken'); const expiresAt = localStorage.getItem('expiresAt'); if (token && expiresAt) { const currentTime = Date.now(); if (currentTime < expiresAt) { setIsAuthenticated(true); // 设置自动登出计时器 const timeRemaining = expiresAt - currentTime; logoutTimer = setTimeout(handleLogout, timeRemaining); } else { handleLogout(); } } // 组件卸载时清除计时器,避免内存泄漏 return () => clearTimeout(logoutTimer); }, []); // 监听页面可见性,切换回页面时检查是否过期 useEffect(() => { const handleVisibilityChange = () => { if (document.visibilityState === 'visible') { const expiresAt = localStorage.getItem('expiresAt'); if (expiresAt && Date.now() >= expiresAt) { handleLogout(); } } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => document.removeEventListener('visibilitychange', handleVisibilityChange); }, []); const handleLogout = () => { localStorage.removeItem('authToken'); localStorage.removeItem('expiresAt'); setIsAuthenticated(false); clearTimeout(logoutTimer); // 跳转登录页 window.location.href = '/login'; }; return ( <AuthContext.Provider value={{ isAuthenticated, handleLogout }}> {children} </AuthContext.Provider> ); }; // 自定义Hook快速获取AuthContext export const useAuth = () => useContext(AuthContext);
3. 封装React Router路由守卫
创建ProtectedRoute组件,拦截需要登录权限的路由,校验token有效性,无效则重定向到登录页。
import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from './AuthContext'; export const ProtectedRoute = () => { const { isAuthenticated } = useAuth(); const token = localStorage.getItem('authToken'); const expiresAt = localStorage.getItem('expiresAt'); // 双重校验:全局状态+localStorage过期时间 const isTokenValid = token && expiresAt && Date.now() < expiresAt; if (!isAuthenticated || !isTokenValid) { return <Navigate to="/login" replace />; } return <Outlet />; };
在路由配置中使用:
import { Routes, Route } from 'react-router-dom'; import { ProtectedRoute } from './ProtectedRoute'; import Login from './Login'; import Dashboard from './Dashboard'; import Profile from './Profile'; function App() { return ( <AuthProvider> <Routes> <Route path="/login" element={<Login />} /> {/* 需登录的路由统一放在ProtectedRoute下 */} <Route element={<ProtectedRoute />}> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> </Route> <Route path="/" element={<Navigate to="/dashboard" replace />} /> </Routes> </AuthProvider> ); }
4. 关键注意事项
- 后端校验优先:前端自动登出仅为体验优化,所有接口请求时后端必须严格校验JWT有效性,防止前端篡改过期时间绕过校验。
- 异常场景处理:若接口返回401(token失效),需立即触发登出逻辑,覆盖前端校验遗漏的情况。
- 内存泄漏防范:组件卸载、登出时必须清除
setTimeout计时器,避免内存泄漏。
内容的提问来源于stack exchange,提问作者vance
相关产品推荐
相关产品推荐

