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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:45:33