如何在React中实现页面刷新后保持用户登录(React+Express+MongoDB方案)
使用React、Express和MongoDB实现持久化用户认证(保持登录至主动登出)
核心方案是通过JWT(JSON Web Token)+ HttpOnly Cookie实现:用户登录成功后,后端生成JWT并写入HttpOnly Cookie(前端无法直接操作,避免XSS攻击),前端后续请求自动携带该Cookie,后端验证JWT有效性;登出时后端清除Cookie,前端同步清除登录状态。
一、后端实现(Express + MongoDB)
1. 安装依赖
npm install express mongoose bcryptjs jsonwebtoken cookie-parser cors dotenv
2. 定义MongoDB用户模型
创建models/User.js:
const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true }, password: { type: String, required: true, minlength: 6 } }); // 保存前自动哈希密码 userSchema.pre('save', async function(next) { if (!this.isModified('password')) return next(); this.password = await bcrypt.hash(this.password, 12); next(); }); // 密码验证方法 userSchema.methods.comparePassword = async function(candidatePassword) { return await bcrypt.compare(candidatePassword, this.password); }; module.exports = mongoose.model('User', userSchema);
3. 配置Express基础中间件
在server.js中初始化:
const express = require('express'); const mongoose = require('mongoose'); const cookieParser = require('cookie-parser'); const cors = require('cors'); require('dotenv').config(); const app = express(); // 解析请求体与Cookie app.use(express.json()); app.use(cookieParser()); // 配置CORS,允许前端携带Cookie app.use(cors({ origin: process.env.CLIENT_URL, credentials: true })); // 连接MongoDB mongoose.connect(process.env.MONGO_URI) .then(() => console.log('MongoDB连接成功')) .catch(err => console.error('MongoDB连接失败:', err)); // 挂载认证路由 app.use('/api/auth', require('./routes/auth')); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`服务器运行在端口${PORT}`));
4. 编写认证路由(登录、验证、登出)
创建routes/auth.js:
const express = require('express'); const jwt = require('jsonwebtoken'); const User = require('../models/User'); const authMiddleware = require('../middleware/auth'); const router = express.Router(); // 登录接口 router.post('/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user || !(await user.comparePassword(password))) { return res.status(401).json({ message: '邮箱或密码错误' }); } // 生成7天有效期的JWT const token = jwt.sign( { id: user._id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' } ); // 设置HttpOnly Cookie res.cookie('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPS sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 // 7天有效期 }); res.json({ message: '登录成功', user: { id: user._id, email: user.email } }); } catch (err) { res.status(500).json({ message: '服务器错误' }); } }); // 验证当前登录用户(页面刷新时恢复状态) router.get('/me', authMiddleware, (req, res) => { res.json({ user: req.user }); }); // 登出接口 router.post('/logout', (req, res) => { res.clearCookie('token', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' }); res.json({ message: '登出成功' }); }); module.exports = router;
5. JWT验证中间件
创建middleware/auth.js:
const jwt = require('jsonwebtoken'); const User = require('../models/User'); const authMiddleware = async (req, res, next) => { try { const token = req.cookies.token; if (!token) return res.status(401).json({ message: '未授权' }); const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = await User.findById(decoded.id).select('-password'); // 不返回密码字段 if (!user) return res.status(401).json({ message: '用户不存在' }); req.user = user; next(); } catch (err) { res.status(401).json({ message: '无效的登录凭证' }); } }; module.exports = authMiddleware;
二、前端实现(React)
1. 登录请求处理
登录组件示例:
import { useState } from 'react'; const Login = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleLogin = async (e) => { e.preventDefault(); try { const res = await fetch(`${process.env.REACT_APP_API_URL}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // 必须携带Cookie body: JSON.stringify({ email, password }) }); const data = await res.json(); if (res.ok) { // 更新全局状态,这里用localStorage临时存储,推荐用Context/Redux localStorage.setItem('user', JSON.stringify(data.user)); window.location.href = '/dashboard'; } else { alert(data.message); } } catch (err) { alert('登录失败,请稍后重试'); } }; return ( <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> ); }; export default Login;
2. 全局用户状态管理(Context API)
创建context/AuthContext.js:
import { createContext, useContext, useState, useEffect } from 'react'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // 页面刷新时验证登录状态 useEffect(() => { const checkAuth = async () => { try { const res = await fetch(`${process.env.REACT_APP_API_URL}/api/auth/me`, { credentials: 'include' }); if (res.ok) { const data = await res.json(); setUser(data.user); } } catch (err) { console.error('验证登录状态失败:', err); } finally { setLoading(false); } }; checkAuth(); }, []); // 登出函数 const logout = async () => { try { await fetch(`${process.env.REACT_APP_API_URL}/api/auth/logout`, { method: 'POST', credentials: 'include' }); setUser(null); localStorage.removeItem('user'); window.location.href = '/login'; } catch (err) { alert('登出失败'); } }; return ( <AuthContext.Provider value={{ user, loading, logout }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
在App.js中包裹应用:
import { AuthProvider } from './context/AuthContext'; import { Routes, Route } from 'react-router-dom'; import Login from './components/Login'; import Dashboard from './components/Dashboard'; import ProtectedRoute from './components/ProtectedRoute'; function App() { return ( <AuthProvider> <Routes> <Route path="/login" element={<Login />} /> <Route path="/dashboard" element={ <ProtectedRoute> <Dashboard /> </ProtectedRoute> } /> </Routes> </AuthProvider> ); } export default App;
3. 路由守卫组件
创建components/ProtectedRoute.js:
import { Navigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; const ProtectedRoute = ({ children }) => { const { user, loading } = useAuth(); if (loading) { return <div>加载中...</div>; } return user ? children : <Navigate to="/login" />; }; export default ProtectedRoute;
4. 登出功能示例
Dashboard组件中添加登出按钮:
import { useAuth } from '../context/AuthContext'; const Dashboard = () => { const { user, logout } = useAuth(); return ( <div> <h1>欢迎回来,{user.email}</h1> <button onClick={logout}>登出</button> </div> ); }; export default Dashboard;
关键注意事项
- 环境变量:JWT密钥、MongoDB连接字符串、前端域名需存放在
.env文件,禁止硬编码。 - 生产环境配置:必须启用HTTPS,否则
secure: true的Cookie无法生效;CORS的origin需设置为实际前端域名,不能用*。 - 安全性:HttpOnly Cookie避免XSS攻击窃取token;密码用bcrypt哈希存储,绝不明文保存。
内容的提问来源于stack exchange,提问作者Hetarth7
相关产品推荐
相关产品推荐

