Node+React项目跨路由Express Session ID不一致排查
问题描述
发起POST登录请求、登录完成后发起GET业务请求时,两次请求对应的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
相关产品推荐
相关产品推荐

