使用express-session时req.session未定义且浏览器无法设置Cookie求助
问题与需求
现有问题
- 登录接口内打印
req.session.user正常,但其他接口调用时返回undefined - Postman登录可成功创建Cookie,但浏览器登录后应用Cookie无变化
需求
将用户信息和JWT令牌通过GET类型/login接口返回,存入前端Redux状态,且不使用localStorage或sessionStorage存储登录数据
解决方案
后端代码修复
1. index.js 核心调整
主要修复Session配置错误,解决跨域Cookie传递和Session持久化问题:
const express = require('express'); const app = express(); const cors = require("cors"); const port = 5000; const dbConnect = require("./db/dbConnect"); const path = require('path'); const bodyParser = require('body-parser') const cookieParser = require('cookie-parser'); const session = require('express-session'); const MongoDBStore = require('connect-mongodb-session')(session); const http = require('http'); const { Server } = require('socket.io'); const dotenv = require("./dotenv"); dotenv.config(); dbConnect(); app.set("trust proxy", 1); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], credentials: true }, }); app.use( cors({ origin: ["http://localhost:3000"], methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], credentials: true, optionsSuccessStatus: 200 }) ); app.use(express.json()); app.use(bodyParser.json()); app.use('/images', express.static(path.join(__dirname, 'images'))); app.use(bodyParser.urlencoded({extended: true})); // 单独创建sessions集合存储会话,避免和用户数据混淆 const store = MongoDBStore({ collection: 'sessions', uri: process.env.MONGO_URL, expires: 24 * 60 * 60 * 1000 }) store.on('error', function(error) { console.log('Session store error:', error); }); app.use(cookieParser()); const isProduction = process.env.NODE_ENV === 'production'; app.use(session({ cookie: { secure: isProduction, // 开发环境HTTP设为false,生产环境HTTPS设为true sameSite: isProduction ? "none" : "lax", maxAge: 24 * 60 * 60 * 1000, httpOnly: true, // 防止XSS攻击,前端无法直接读取Cookie domain: isProduction ? '.yourdomain.com' : 'localhost' }, key: "sessionId", store: store, secret: process.env.SESSION_SECRET || 'your-strong-secret-key', saveUninitialized: false, // 未初始化的会话不存储,提升安全性 resave: false, })); const authRoute = require("./routes/auth"); const userRoute = require("./routes/user"); const producttRoute = require("./routes/product"); const cartRoute = require("./routes/cart"); const orderRoute = require("./routes/order"); const stripeRoute = require("./routes/stripe"); app.use(authRoute); app.use(userRoute); app.use(producttRoute); app.use(cartRoute); app.use(orderRoute); app.use(stripeRoute); server.listen(process.env.PORT || port, () => { console.log(`Listening on port ${port}`); })
2. Auth路由修改
调整登录逻辑确保Session正确持久化,新增GET /login接口返回用户信息和JWT:
const express = require("express"); const router = express.Router(); const User = require("../models/User"); const CryptoJs = require("crypto-js"); const jwt = require("jsonwebtoken"); const Register = async (req, res) => { const newUser = new User({ username: req.body.username, email: req.body.email, password: CryptoJs.AES.encrypt(req.body.password, process.env.PASS_SEC).toString(), }); try { const savedUser = await newUser.save(); const { password, ...others } = savedUser._doc; res.status(201).json(others); } catch (err) { res.status(500).json(err); } } const Login = async (req, res) => { try { const user = await User.findOne({ username: req.body.username }); if (!user) return res.status(401).json("Wrong Credentials!"); const hashedPassword = CryptoJs.AES.decrypt(user.password, process.env.PASS_SEC); const OriginalPassword = hashedPassword.toString(CryptoJs.enc.Utf8); if (OriginalPassword !== req.body.password) { return res.status(401).json("Wrong Credentials!"); } const accessToken = jwt.sign({ id: user._id, isAdmin: user.isAdmin }, process.env.JWT_SEC, { expiresIn: "3d"}); const { password, ...userInfo } = user._doc; // 保存用户信息到Session req.session.user = { ...userInfo, accessToken }; // 手动触发Session保存,确保写入数据库 req.session.save((err) => { if (err) { console.error('Session save error:', err); return res.status(500).json("Failed to save session"); } res.status(200).json({ ...userInfo, accessToken }); }); } catch(err) { res.status(500).json(err); console.log(err); } } // GET /login:返回当前登录用户信息和JWT const getUserInfo = (req, res) => { if (!req.session.user) { return res.status(401).json("Not logged in"); } res.status(200).json(req.session.user); } // 登出接口:销毁Session并清除Cookie const Logout = (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).json("Failed to logout"); } res.clearCookie("sessionId"); res.status(200).json("Logged out successfully"); }); } router.post("/register", Register); router.post("/login", Login); router.get("/login", getUserInfo); router.post("/logout", Logout); module.exports = router;
前端Redux示例代码
1. Auth Slice (authSlice.js)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import axios from 'axios'; // 全局配置axios,自动携带Cookie axios.defaults.withCredentials = true; // 登录请求 export const login = createAsyncThunk( 'auth/login', async (credentials, { rejectWithValue }) => { try { const response = await axios.post('http://localhost:5000/login', credentials); return response.data; } catch (error) { return rejectWithValue(error.response.data); } } ); // 获取当前登录用户信息 export const fetchUser = createAsyncThunk( 'auth/fetchUser', async (_, { rejectWithValue }) => { try { const response = await axios.get('http://localhost:5000/login'); return response.data; } catch (error) { return rejectWithValue(error.response.data); } } ); // 登出请求 export const logout = createAsyncThunk( 'auth/logout', async (_, { rejectWithValue }) => { try { await axios.post('http://localhost:5000/logout'); return; } catch (error) { return rejectWithValue(error.response.data); } } ); const authSlice = createSlice({ name: 'auth', initialState: { user: null, accessToken: null, loading: false, error: null }, reducers: {}, extraReducers: (builder) => { builder // 登录状态处理 .addCase(login.pending, (state) => { state.loading = true; state.error = null; }) .addCase(login.fulfilled, (state, action) => { state.loading = false; state.user = { ...action.payload, accessToken: undefined }; state.accessToken = action.payload.accessToken; }) .addCase(login.rejected, (state, action) => { state.loading = false; state.error = action.payload; }) // 获取用户信息状态处理 .addCase(fetchUser.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchUser.fulfilled, (state, action) => { state.loading = false; state.user = { ...action.payload, accessToken: undefined }; state.accessToken = action.payload.accessToken; }) .addCase(fetchUser.rejected, (state, action) => { state.loading = false; state.error = action.payload; state.user = null; state.accessToken = null; }) // 登出状态处理 .addCase(logout.fulfilled, (state) => { state.user = null; state.accessToken = null; state.error = null; }); } }); export default authSlice.reducer;
2. Redux Store配置 (store.js)
import { configureStore } from '@reduxjs/toolkit'; import authReducer from './authSlice'; export const store = configureStore({ reducer: { auth: authReducer } });
3. 页面使用示例
import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { login, fetchUser, logout } from './authSlice'; function App() { const dispatch = useDispatch(); const { user, accessToken, loading, error } = useSelector(state => state.auth); // 页面加载时自动获取登录状态 useEffect(() => { dispatch(fetchUser()); }, [dispatch]); const handleLogin = async () => { await dispatch(login({ username: 'test', password: 'test123' })); }; const handleLogout = async () => { await dispatch(logout()); }; return ( <div> {loading && <p>Loading...</p>} {error && <p className="text-red-500">{error}</p>} {user ? ( <div> <h2>Welcome, {user.username}!</h2> <p>Admin Status: {user.isAdmin ? 'Yes' : 'No'}</p> <button onClick={handleLogout} className="mt-4 px-4 py-2 bg-red-500 text-white rounded"> Logout </button> </div> ) : ( <button onClick={handleLogin} className="px-4 py-2 bg-blue-500 text-white rounded"> Login </button> )} </div> ); } export default App;
关键修复说明
Session配置修正:
- 开发环境关闭
secure选项(localhost用HTTP),生产环境开启 - 用
NODE_ENV区分环境配置sameSite - 单独创建
sessions集合存储会话,避免污染用户数据 - 增加
httpOnly保护Cookie,防止XSS攻击
- 开发环境关闭
Session持久化修复:
- 登录接口中调用
req.session.save()确保会话写入数据库 - 调整
saveUninitialized为false,避免存储空会话
- 登录接口中调用
前端Cookie传递:
- 全局配置axios
withCredentials: true,确保请求自动携带Cookie - 页面加载时调用
fetchUser获取登录状态,无需依赖本地存储
- 全局配置axios
内容的提问来源于stack exchange,提问作者RAHMAN DHIKA
相关产品推荐
相关产品推荐

