JWT路由私有化:页面重载后状态持久化失败及Token丢失问题
页面重载后JWT路由验证失效,Auth状态结构异常
我用JWT实现路由私有化,登录时用户数据和Token能正常存入AuthProvider的state,但页面重载后,数据被存入state的子字段,Token丢失,页面重定向到登录页。正常应该重载后用户数据和Token保留,因为这是私有路由操作的必要条件。
相关代码
AuthProvider
import { useState, useEffect, createContext } from "react"; import clientAxios from "../config/ClientAxios"; import { useNavigate } from "react-router-dom"; const AuthContext = createContext(); const AuthProvider = ({ children }) => { const [auth, setAuth] = useState({}); const navigate = useNavigate(); useEffect(() => { const authUser = async () => { const token = localStorage.getItem("token"); if (!token) { return; } const config = { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }; try { const data = await clientAxios("/usuarios/perfil", config); setAuth(data); navigate('/inicio') } catch (error) { setAuth({}); } }; authUser(); }, []); return ( <AuthContext.Provider value={{ auth, setAuth, }} > {children} </AuthContext.Provider> ); }; export { AuthProvider }; export default AuthContext;
ProtectedRoute
import React from "react"; import { Outlet, Navigate } from "react-router-dom"; import useAuth from "../hooks/useAuth"; import Header from "../components/Header"; const ProtectedRoute = () => { const { auth } = useAuth(); return( <div> {auth._id ? ( <div className="bg-gray-100"> <Header/> <div className="md:min-h-screen"> <main> <Outlet/> </main> </div> </div> ) :<Navigate to="/"/> } </div> ) }; export default ProtectedRoute;
useAuth
import { useContext } from "react"; import AuthContext from "../context/AuthProvider"; const useAuth = () => { return useContext(AuthContext); }; export default useAuth;
Login
import { Link, useNavigate } from "react-router-dom"; import { useState } from "react"; import Alert from "../components/Alert"; import clientAxios from "../config/ClientAxios"; import useAuth from "../hooks/useAuth"; import { AuthProvider } from "../context/AuthProvider"; const Login = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [alert, setAlert] = useState({}); const {setAuth}=useAuth() const navigate=useNavigate() const handleSubmit = async (e) => { e.preventDefault(); if ([email, password].includes("")) { setAlert({ msg: "Hay uno o mas campos vacios", error: true, }); return; } try { const {data}=await clientAxios.post('/usuarios/',{email,password}) setAlert({}) localStorage.setItem('token',data.token) setAuth(data) navigate('/inicio') } catch (error) { setAlert({ msg:error.response.data.msg, error:true }) } }; const { msg } = alert; return ( <div> <h1 className="text-sky-600 font-black text-4xl capitalize mt-3"> Bienvenido de vuelta ! </h1> <h3 className="text-gray-400 text-xl font-black"> {" "} Inicia sesion y mejora tu <span className="text-sky-500"> ambiente laboral</span> </h3> {msg && <Alert alert={alert} />} <form onSubmit={handleSubmit} className="my-5"> <label htmlFor="email" className=" font-bold uppercase block text-xl text-gray-600" > Email </label> <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email Laboral" className="w-full mt-3 p-3 border rounded-xl bg-gray-200 cursor-pointer" /> <label htmlFor="password" className=" font-bold uppercase block text-xl text-gray-600 mt-3" > Contraseña </label> <input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Contraseña" className="w-full mt-3 p-3 border rounded-xl bg-gray-200 cursor-pointer" /> <input type="submit" value="Iniciar Sesion" className="bg-sky-700 text-white w-full py-3 mt-5 font-bold rounded-md uppercase hover:cursor-pointer hover:bg-sky-900 transition-colors" /> </form> <nav> <Link className="hover:underline block text-center my-5 text-slate-500 font-semibold uppercase text-sm" to="forget-password" > Cambia tu Contraseña </Link> </nav> </div> ); }; export default Login;
状态截图
- 登录后正常状态:
(此状态应在页面重载后保持) - 页面重载后的异常状态:
(重载后返回登录页,数据存入state子字段,无法通过auth._id验证)
问题分析与解决方案
核心问题
- API返回结构不一致:登录时
/usuarios/接口返回的是直接包含_id的用户对象,但重载时调用的/usuarios/perfil接口返回的数据可能嵌套了一层(比如response.data.data才是用户信息),导致auth状态结构异常,auth._id不存在。 - 状态初始化缺失:页面重载时
auth初始化为空对象,ProtectedRoute会先触发重定向,等useEffect中的接口请求完成时已经来不及。 - 错误处理不彻底:接口请求失败时没有清除localStorage中的无效token,导致后续验证逻辑混乱。
解决步骤
1. 统一API数据结构处理
检查/usuarios/perfil接口的返回格式,确保和登录接口一致。如果后端返回嵌套结构,调整AuthProvider中的数据提取逻辑:
// AuthProvider的useEffect中 try { const response = await clientAxios("/usuarios/perfil", config); // 假设后端返回数据嵌套在response.data里,根据实际结构调整 const userData = response.data; setAuth(userData); } catch (error) { localStorage.removeItem("token"); setAuth({}); navigate('/'); }
2. 优化状态初始化与持久化
页面重载时先从localStorage恢复基础信息,避免短暂未认证状态,同时封装setAuth同步更新localStorage:
const AuthProvider = ({ children }) => { // 初始化时从localStorage恢复状态 const initialAuth = () => { const token = localStorage.getItem("token"); const userInfo = localStorage.getItem("userInfo"); return token && userInfo ? JSON.parse(userInfo) : {}; }; const [auth, setAuth] = useState(initialAuth()); const navigate = useNavigate(); useEffect(() => { const authUser = async () => { const token = localStorage.getItem("token"); if (!token) { setAuth({}); return; } const config = { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }; try { const response = await clientAxios("/usuarios/perfil", config); const userData = response.data; setAuth(userData); localStorage.setItem("userInfo", JSON.stringify(userData)); } catch (error) { localStorage.removeItem("token"); localStorage.removeItem("userInfo"); setAuth({}); navigate('/', { replace: true }); } }; authUser(); }, [navigate]); // 封装同步localStorage的setAuth const updateAuth = (newAuth) => { setAuth(newAuth); if (newAuth.token) { localStorage.setItem("token", newAuth.token); localStorage.setItem("userInfo", JSON.stringify(newAuth)); } else { localStorage.removeItem("token"); localStorage.removeItem("userInfo"); } }; return ( <AuthContext.Provider value={{ auth, setAuth: updateAuth, }} > {children} </AuthContext.Provider> ); };
3. 增强ProtectedRoute验证逻辑
增加token直接验证,避免仅依赖auth._id,同时添加加载状态避免错误跳转:
import { useState, useEffect } from "react"; const ProtectedRoute = () => { const { auth } = useAuth(); const token = localStorage.getItem("token"); const [isLoading, setIsLoading] = useState(true); useEffect(() => { // 等待auth状态初始化完成 setIsLoading(false); }, [auth]); if (isLoading) { return <div>Cargando...</div>; // 或加载组件 } return ( <div> {token && auth._id ? ( <div className="bg-gray-100"> <Header/> <div className="md:min-h-screen"> <main> <Outlet/> </main> </div> </div> ) :<Navigate to="/" replace/> } </div> ) };
4. 修复登录组件冗余代码
移除Login组件中多余的AuthProvider导入,确保组件正确使用全局上下文:
// Login组件中删除此行 import { AuthProvider } from "../context/AuthProvider";
内容的提问来源于stack exchange,提问作者B3rriess
相关产品推荐
相关产品推荐

