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

Node+React项目跨路由Express Session ID不一致排查

问题描述

发起POST登录请求、登录完成后发起GET业务请求时,两次请求对应的Session ID存在差异:
相同账号密码下Session ID不一致

  • 前端采用React构建单页应用(SPA),所有URL子路由由react-router处理,路由根路径为/
  • 前端package.json中配置代理项"proxy":"http://localhost:5000",用于转发请求到后端服务
  • 原方案计划通过Session有效性校验结合React Context维护全局用户登录状态,但始终无法解决Session跨请求不一致问题,查阅大量同类场景帖子均未找到可行方案。
相关代码

前端请求代码

import './App.css';
import { Outlet } from 'react-router-dom';
import axios from 'axios';
import { useState } from 'react';
import { Button } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import Navbar from './Components/Navbar';
import { useNavigate } from 'react-router-dom';

function App() {
  const [data, setData] = useState({})
  let navigate = useNavigate();
  axios.defaults.withCredentials = true

const makeGet = () => {
  let userData = { username: 'johnson72', password: 'a' }
  axios.get('/api/dashboard')
    .then((res) => console.log(res.status))
    .catch((err) => err.response.status === 500 ? navigate('/login', { replace: true }) && console.log('8err', err) : console.log(err))
}

const loginPost = () => {
  let userData = { username: 'johnson72', password: 'a' }
  axios.post('/login', userData, {withCredentials: true, credentials: 'include'})
    .then((res) => console.log(res.status))
    .catch((err) => console.log(err))
}

const handleGet = (e) => {
  e.preventDefault();
  makeGet();
}
const handleLogin = (e) => {
  e.preventDefault();
  loginPost();
}
  return (
    <>
    <div className="App">
      <div className='jumbotron-fluid d-flex align-items-center' style={{backgroundColor:'pink', height:'15rem'}}>
          <div className='container'>
            <Navbar />
            <div className='row'>
              <h3>{data?.username ? 'Hello ' + data?.username + ' ' + data?.email + '!' : ''}</h3>
            </div>
          </div>
      </div>
    <Outlet />
    </div>
    <Button variant='dark' onClick={handleGet}>Get</Button>
    <Button variant='dark' onClick={loginPost}>Login</Button>
    </>
  );
}
export default App;

后端服务代码

import express from 'express';
import mongoose from "mongoose";
import bodyParser from 'body-parser';
import routes from './routes/userRoutes';
import passport from 'passport'
import { User } from './datamodels/schema';
import session from 'express-session'
import cors from 'cors';
import cookieParser from 'cookie-parser';


const app = express();
const PORT = process.env.PORT || 5000;

mongoose.Promise = global.Promise
mongoose.connect('mongodb://localhost/test5', {
    useNewUrlParser: true,
    useUnifiedTopology: true
})

app.use(express.json());
// app.use(cookieParser())
app.use(bodyParser.urlencoded({ extended: true }))

app.use(session({ 
    secret: "cats",
    // cookie: { maxAge: 600000 },
    resave: false,
    saveUninitialized: false,
}));
app.use(passport.initialize());
app.use(passport.session());

passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser);

app.post('/login', passport.authenticate('local',{failureMessage: true}), (req, res) => {
    console.log('you have hit the login route');
    console.log('req is auth ', req.isAuthenticated());
    console.log(req.user.username);
    console.log(req.session);
    console.log(req.session.id);
    
})
app.get('/api/dashboard', (req, res) => {
    // console.log('req is auth api / dashboard ', req.isAuthenticated());
    console.log('session id from api dash ', req.session.id);
})

function isLoggedIn(req, res, next) {
    if (req.user) {
        console.log(`User Is Logged In!`)
        return next();
    }
    res.sendStatus(500)
}

routes(app); // chained route handler 
app.listen(PORT, () => console.log(`Server listening @ ${PORT}`))
问题根因与修复方案

核心问题点

  • 后端两个接口均未返回响应:/login和/api/dashboard接口只打印了日志,没有给客户端返回任何响应,携带Session ID的Set-Cookie响应头无法被浏览器正常接收存储,后续请求会被识别为全新会话,生成新的Session ID。
  • Axios全局配置位置错误:axios.defaults.withCredentials = true写在App组件函数内部,组件每次重渲染都会重复执行全局配置,存在配置被覆盖的风险。
  • 冗余参数与缺失配置:POST请求中传入的credentials: 'include'是fetch API的配置项,axios中不生效;后端引入了cors和cookie-parser但未实际启用,Session Cookie未显式配置属性,在代理场景下容易出现无法留存的问题。

注意:Passport在登录认证成功后会默认重新生成Session ID用于防御会话固定攻击,这是正常逻辑,只要后续请求携带的是登录后返回的新Session ID,不需要额外处理。

修复步骤

1. 修复后端配置与接口逻辑

首先启用被注释的依赖,补全CORS、Session Cookie配置,放在路由注册之前:

app.use(express.json());
app.use(cookieParser()) // 启用cookie解析
app.use(bodyParser.urlencoded({ extended: true }))
// 配置CORS,适配前后端分离场景
app.use(cors({
  origin: 'http://localhost:3000', // 替换为前端实际访问地址
  credentials: true
}))
// 补全Session Cookie配置
app.use(session({ 
    secret: "cats",
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      sameSite: 'lax',
      secure: false, // 本地HTTP环境设为false,线上HTTPS环境改为true
      maxAge: 24 * 60 * 60 * 1000 // Cookie有效期1天,可按需调整
    }
}));

修改两个接口的处理逻辑,确保请求正常返回响应:

app.post('/login', passport.authenticate('local',{failureMessage: true}), (req, res) => {
    console.log('you have hit the login route');
    console.log('req is auth ', req.isAuthenticated());
    console.log(req.user.username);
    console.log(req.session);
    console.log(req.session.id);
    // 返回登录成功响应
    res.status(200).json({
      code: 0,
      msg: '登录成功',
      user: req.user
    })
})
app.get('/api/dashboard', (req, res) => {
    console.log('session id from api dash ', req.session.id);
    // 返回业务响应
    res.status(200).json({
      code: 0,
      isAuth: req.isAuthenticated()
    })
})

2. 修复前端Axios配置

不要在组件内部修改axios全局默认配置,建议单独封装axios实例统一管理配置:
新建src/request.js文件:

import axios from 'axios'
const service = axios.create({
  baseURL: '/',
  withCredentials: true // 全局统一配置跨域携带Cookie
})
export default service

修改App.js中的axios引入与请求逻辑,删除组件内部的axios.defaults.withCredentials = true配置,移除POST请求中不生效的credentials参数:

// 替换原axios引入
import axios from './request'

// 修改登录请求方法
const loginPost = () => {
  let userData = { username: 'johnson72', password: 'a' }
  axios.post('/login', userData)
    .then((res) => console.log(res.status))
    .catch((err) => console.log(err))
}

3. 验证结果

重启前后端服务,先调用登录接口,在浏览器开发者工具Network面板中查看登录响应头,确认存在Set-Cookie: connect.sid=xxx字段;再调用dashboard接口,查看请求头Cookie字段是否携带了对应的connect.sid,此时两个接口打印的Session ID将保持一致,登录状态校验逻辑可正常运行。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:03:28