You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 15:30:58