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

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

关键修复说明

  1. Session配置修正:

    • 开发环境关闭secure选项(localhost用HTTP),生产环境开启
    • 用NODE_ENV区分环境配置sameSite
    • 单独创建sessions集合存储会话,避免污染用户数据
    • 增加httpOnly保护Cookie,防止XSS攻击
  2. Session持久化修复:

    • 登录接口中调用req.session.save()确保会话写入数据库
    • 调整saveUninitialized为false,避免存储空会话
  3. 前端Cookie传递:

    • 全局配置axioswithCredentials: true,确保请求自动携带Cookie
    • 页面加载时调用fetchUser获取登录状态,无需依赖本地存储

内容的提问来源于stack exchange,提问作者RAHMAN DHIKA

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 19:14:55