React前端JWT认证:路由保护及Cookie存储Token实现疑问
我来帮你一步步搞定React里的JWT路由保护和Token处理,基于你现有的代码来扩展:
1. 先完善JWT Token的核心处理逻辑
你当前的代码只是用一个布尔值标记认证状态,实际JWT场景下应该存储后端返回的真实Token,还要验证Token的有效性(比如是否过期)。修改你的authService:
import Cookies from 'universal-cookie'; import jwtDecode from 'jwt-decode'; // 需要先安装:npm install jwt-decode const cookies = new Cookies(); const authService = { // 获取存储的JWT Token getToken() { return cookies.get('authToken'); }, // 判断是否已认证:检查Token存在且未过期 isAuthenticated() { const token = this.getToken(); if (!token) return false; try { const decoded = jwtDecode(token); // JWT的exp字段是过期时间戳(单位秒),转成毫秒和当前时间对比 return decoded.exp * 1000 > Date.now(); } catch (err) { // Token无效(比如篡改、格式错误),直接清除并返回未认证 this.signOut(() => {}); return false; } }, // 登录时存储后端返回的JWT Token signIn(data, cb) { // 假设后端返回的data里包含token字段 cookies.set('authToken', data.token, { path: '/', expires: new Date(Date.now() + 3600 * 1000) // 和Token过期时间保持一致,比如1小时 }); setTimeout(cb, 100); }, // 登出时清除Token signOut(cb) { cookies.remove('authToken', { path: '/' }); setTimeout(cb, 100); } }; export default authService;
这里的关键改进:
- 存储实际的JWT Token而非布尔值,让后端能验证用户身份
- 用
jwt-decode解析Token,检查是否过期,避免使用无效Token - 给Cookie设置合理的过期时间,和Token有效期对齐
2. 实现路由保护(React Router v6)
React Router v6推荐用自定义路由组件来保护私有路由,我们创建一个ProtectedRoute组件:
import { Navigate, Outlet } from 'react-router-dom'; import authService from './authService'; const ProtectedRoute = () => { const isAuth = authService.isAuthenticated(); // 未认证就跳登录页,已认证则渲染子路由内容 return isAuth ? <Outlet /> : <Navigate to="/login" replace />; };
然后在路由配置中使用这个组件包裹所有需要保护的页面:
import { BrowserRouter as Router, 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 ( <Router> <Routes> {/* 公开可访问的路由 */} <Route path="/login" element={<Login />} /> {/* 受保护的路由组:只有登录后才能访问 */} <Route element={<ProtectedRoute />}> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> {/* 其他私有页面都放在这个组里 */} </Route> {/* 默认跳转:未登录跳登录,已登录跳仪表盘 */} <Route path="/" element={<Navigate to={authService.isAuthenticated() ? "/dashboard" : "/login"} replace />} /> </Routes> </Router> ); }
如果用的是React Router v5,写法会略有不同,但v6的这种Outlet方式更简洁灵活,建议升级到v6。
3. 登录/登出组件的实际使用示例
登录组件(Login.jsx)
import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import authService from './authService'; const Login = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const navigate = useNavigate(); const handleLogin = async (e) => { e.preventDefault(); try { // 调用后端登录接口获取JWT Token const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { // 存储Token后跳转到仪表盘 authService.signIn(data, () => { navigate('/dashboard'); }); } else { alert(data.message || '登录失败,请检查账号密码'); } } catch (err) { alert('网络错误,请稍后重试'); } }; return ( <div className="login-container"> <form onSubmit={handleLogin}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="请输入邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="请输入密码" required /> <button type="submit">登录</button> </form> </div> ); };
登出功能示例(比如在Header组件中)
import { useNavigate } from 'react-router-dom'; import authService from './authService'; const Header = () => { const navigate = useNavigate(); const handleSignOut = () => { authService.signOut(() => { navigate('/login'); }); }; return ( <header> <h2>我的应用</h2> <button onClick={handleSignOut}>登出</button> </header> ); };
4. 生产环境的额外注意事项
- Token刷新:Token过期前要调用后端的刷新接口获取新Token,避免用户频繁登录。可以在
isAuthenticated里检测Token即将过期时自动刷新。 - Cookie安全性:生产环境一定要给Cookie加这些属性:
cookies.set('authToken', data.token, { path: '/', expires: new Date(Date.now() + 3600 * 1000), secure: true, // 仅HTTPS下传输 sameSite: 'strict', // 防止CSRF攻击 httpOnly: true // 防止XSS攻击(但httpOnly的Cookie前端无法读取,需要后端配合处理) }); - API请求携带Token:每次调用需要认证的接口时,要在请求头里带上Token:
const token = authService.getToken(); const response = await fetch('/api/protected-data', { headers: { 'Authorization': `Bearer ${token}` } }); - 全局错误处理:封装一个API请求工具,统一处理401错误(Token过期或无效),自动清除Token并跳转到登录页。
内容的提问来源于stack exchange,提问作者bikky barnwal
相关产品推荐
相关产品推荐

