使用axios与express session时req.session.user为undefined的问题排查
Express Session 跨域会话丢失问题排查与解决
问题现象
在React+Express前后端分离架构中使用express-session,已完成以下配置:
- 服务端登录成功后将用户信息存入
req.session - 配置
express-session凭证参数,CORS设置credentials: true - 前端Axios全局配置
withCredentials: true,请求时也单独声明该参数
但后续调用/api/user-profile接口时,req.session.user始终返回undefined,尝试添加中间件无改善。
相关代码与日志
服务端server.mjs代码
import express from 'express'; import session from 'express-session'; import mongoose from 'mongoose'; import cors from 'cors'; import User from './db.mjs'; import path from 'path'; import url from 'url'; import bcrypt from 'bcryptjs'; import bodyParser from 'body-parser'; import router from './routes/user.routes.mjs'; const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 600000, secure: true }, resave: false, saveUninitialized: true, })) app.use( cors({ allowedHeaders: ["authorization", "Content-Type"], exposedHeaders: ["authorization", 'set-cookie'], origin: "*", credentials: true, origin: true, methods: "GET,HEAD,PUT,PATCH,POST,DELETE", preflightContinue: false }) ); app.options({ origin: true, credentials: true, }) app.use('/public', express.static('public')); app.use('/api', router) app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); // 全局挂载用户变量 app.use((req, res, next) => { res.locals.user = req.session.user; next(); }); // 请求日志中间件 app.use((req, res, next) => { console.log(req.method, req.path, req.body); next(); }); // 登录逻辑 const login = (email, password, successCallback, errorCallback) => { const user = User.findOne({'email': email}, function (err, user) { console.log(err,user) if (err) { console.log(err); errorCallback('用户查询失败') } else { if (user === null) { errorCallback('未找到用户'); } else { bcrypt.compare(password, user.password, (err, passwordMatch) => { if (err) { console.log(err); errorCallback("密码校验失败") } else { passwordMatch ? successCallback(user) : errorCallback("密码不匹配"); } }); } } }); }; // 初始化认证会话 const startAuthenticatedSession = (req, user, cb) => { console.log('初始化新认证会话', user); req.session.regenerate(function(err) { if (!err) { req.session.user = user; cb(err); } else { cb(err); } }) }; // 销毁会话 const endAuthenticatedSession = (req, cb) => { req.session.destroy((err) => { cb(err); }); }; // 注册逻辑 const signup = (email, password, errorCallback, successCallback) => { if (password.length < 8) { errorCallback('密码过短'); } User.findOne({'email': email}, function(err, result) { console.log(result); if (result === null) { bcrypt.hash(password, 10, function(err, hash) { if (err) { console.log(err); errorCallback({message: '密码哈希失败'}) } const newUser = new User({ email: email, password: hash }); newUser.save(function(err, user, count) { err ? errorCallback('文档保存错误') : successCallback(user); console.log(req.session.user); }); }); } else { errorCallback('邮箱已存在'); } }) User.findOne({'email': email}); }; // 测试接口 app.get("/api", (req, res) => { res.json({"user": ["userOne", "userTwo", "userThree"]}) }) // 登录接口 app.post("/api/login/", (req, res) => { const { email, password } = req.body; console.log(req.body) function success(newUser) { startAuthenticatedSession(req, newUser, (err) => { if (!err) { req.session.user = newUser; res.send({key:'success'}); } else { res.render('error', {message: '认证出错???'}); } }); } login(email, password, success, (e)=>console.log(e)); })
React前端组件代码
import '../../App.css'; import React from 'react'; import axios from 'axios'; import icon from '../../img/upload.svg'; axios.defaults.withCredentials = true; function handleFile(files) { alert("文件数量: " + files.length); console.log(files); const profileImg = 'profileImg'; console.log(files[0].name); const formData = new FormData(); formData.append(profileImg, files[0]); axios.post("http://localhost:8000/api/user-profile", formData, { headers: { 'Content-Type': 'multipart/form-data' }, withCredentials: true }).then(res => { console.log(res); }); }
打印undefined的接口代码
router.post('/user-profile', upload.single('profileImg'), (req, res, next) => { const url = req.protocol + '://' + req.get('host') console.log(req.file); console.log(req.session.user);
补充:req.session日志信息
Session { cookie: { path: '/', _expires: 2022-12-05T04:56:43.020Z, originalMaxAge: 600000, httpOnly: true, secure: true } }

解决方案
1. 修复CORS配置冲突
当credentials: true时,不能将origin设为通配符*,必须指定前端具体域名,同时移除重复的origin配置项:
app.use( cors({ allowedHeaders: ["authorization", "Content-Type"], exposedHeaders: ["authorization", 'set-cookie'], origin: "http://localhost:3000", // 替换为你的React前端地址 credentials: true, methods: "GET,HEAD,PUT,PATCH,POST,DELETE", preflightContinue: false }) );
2. 调整Session Cookie的secure属性
secure: true表示Cookie仅在HTTPS连接中发送,本地开发使用HTTP协议时,浏览器不会携带该Cookie,导致会话丢失。改为仅生产环境启用:
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 600000, secure: process.env.NODE_ENV === 'production', // 仅HTTPS环境启用 sameSite: 'lax' // 避免跨域Cookie限制 }, resave: false, saveUninitialized: false, // 避免创建空会话 }))
3. 验证会话存储逻辑
在登录接口添加日志,确认req.session.user是否被正确写入:
function success(newUser) { startAuthenticatedSession(req, newUser, (err) => { if (!err) { req.session.user = newUser; console.log('登录后会话用户:', req.session.user); // 新增日志 res.send({key:'success'}); } else { res.render('error', {message: '认证出错???'}); } }); }
4. 确保请求域名一致性
前端请求的域名需与CORS配置的origin完全一致,比如前端用http://localhost:3000,请求时不能写http://127.0.0.1:8000,必须保持域名/IP统一。
内容的提问来源于stack exchange,提问作者bloomsdayforever
相关产品推荐
相关产品推荐

