如何使用Nest.js和JWT实现客户端用户登录状态跨页面刷新跳转持久化
登录状态持久化实现方案
下面分两种主流实现思路,可根据项目安全要求选择:
方案1:前端手动存储Token到Web Storage(实现简单)
适合对安全性要求中等的场景,实现成本极低:
- 第一步:修改
handleSubmit逻辑,登录成功后把返回的access_token存入localStorage(永久存储,关闭浏览器也保留)或sessionStorage(仅当前标签页生效,关闭即销毁)
示例修改代码:const handleSubmit = async (evt: React.FormEvent) => { evt.preventDefault(); try { const res = await fetch('http://localhost:3001/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData), }); const response = await res.json(); // 新增:存储token到本地 localStorage.setItem('access_token', response.access_token); // TODO replace placeholder with .env var const user = jwt.verify(response.access_token, 'placeholder'); setUser(user); history.push('/'); } catch (err) { // Handle error } }; - 第二步:在全局
UserProvider初始化时读取本地存储的Token,恢复登录状态
你需要找到定义UserContext的Provider组件,新增初始化逻辑:const UserProvider = ({ children }) => { const [user, setUser] = useState(null); // 新增:页面加载时校验本地token恢复登录状态 useEffect(() => { const token = localStorage.getItem('access_token'); if (token) { try { const decodedUser = jwt.verify(token, 'placeholder'); // 密钥替换为.env变量 setUser(decodedUser); } catch (err) { // token过期/非法,清理无效数据 localStorage.removeItem('access_token'); setUser(null); } } }, []); return ( <UserContext.Provider value={[user, setUser]}> {children} </UserContext.Provider> ) } - 后续接口请求注意:每次发请求需要在请求头携带Token,示例:
fetch('你的接口地址', { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` } }) - 注意事项:Web Storage存在XSS攻击风险,若被注入恶意脚本可直接读取到Token,需做好页面XSS防护(用户输入转义、禁止加载不可信第三方脚本等)。
方案2:HttpOnly Cookie自动存储(安全性更高,推荐大型项目使用)
该方案将Token交给浏览器自动管理,JS无法读取Token,可完全避免XSS窃取Token的风险:
- 第一步:修改Nest服务端登录接口,返回响应时添加
Set-Cookie响应头,将access_token写入Cookie,配置参数:httpOnly: true:禁止JS读取Cookiesecure: true:生产环境HTTPS下才发送CookiesameSite: 'lax':避免CSRF攻击path: '/':全站生效
- 第二步:放开前端请求中注释的
credentials: "include"配置,所有跨域请求都会自动携带服务端下发的Cookie - 第三步:在全局
UserProvider初始化时,调用后端提供的/api/auth/me接口获取当前登录用户信息,恢复登录状态,示例:const UserProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchCurrentUser = async () => { try { const res = await fetch('http://localhost:3001/api/auth/me', { credentials: 'include' }); if (res.ok) { const userInfo = await res.json(); setUser(userInfo); } } catch (err) { setUser(null); } finally { setLoading(false); } } fetchCurrentUser(); }, []); // 可以加loading状态避免未拿到用户信息时页面跳转异常 if (loading) return <Loading /> return ( <UserContext.Provider value={[user, setUser]}> {children} </UserContext.Provider> ) }
通用补充优化
- 路由跳转不需要额外处理登录状态,只要
UserProvider包裹在路由组件外层,路由切换时Context内的用户状态不会丢失 - 统一处理接口401响应:所有请求返回401状态码时,直接清理本地存储的Token/ Cookie,跳转至登录页,避免过期状态残留
- 可搭配
redux-persist等状态持久化工具使用,如果你项目中用了Redux作为全局状态管理,可直接配置persist规则自动持久化用户状态
内容的提问来源于stack exchange,提问作者Jennifer Miao
相关产品推荐
相关产品推荐

