express-session无法读取跨服务传递的会话Cookie问题排查
问题场景与现象
本地同时运行两个服务:
- 3300端口:SSR Vite Vue应用
- 4400端口:json-server托管模拟Web API的
db.json
基于express-session实现登录认证逻辑:
- 登录时将用户信息存入
req.session并调用req.session.save() - 通过
/me接口获取当前会话用户 - SSR服务的
auth中间件会将浏览器Cookie转发到json-server的/me接口
问题现象:
- Postman直接请求
/login和/me功能正常,能正确返回用户信息 - SSR转发Cookie到
/me时,尽管req.headers.cookie包含connect.sid,但req.session中仅存在cookie配置,无user属性:Session { cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true, sameSite: false } }
核心代码片段
JSON Server 主文件
import bodyParser from 'body-parser'; import cors from 'cors'; import dotenv from 'dotenv'; import express from 'express'; import jsonServer from 'json-server'; import mockLogin from './mocked-auth/login.handler.js'; import mockLogout from './mocked-auth/logout.handler.js'; import me from './mocked-auth/me.handler.js'; import { expressCookieSession } from './mocked-auth/session.middleware.js'; dotenv.config(); const server = jsonServer.create(); const router = jsonServer.router('./json-server/db.json'); const middlewares = jsonServer.defaults(); server.use(cors()); // 原代码:使用子app挂载auth路由和session中间件 const app = express(); app.use(expressCookieSession); app.use(bodyParser.json()); mockLogin(app, router); mockLogout(app); me(app); server.use(app); server.use(middlewares); server.use(router); const port = process.env.JSON_SERVER_PORT; server.listen(port, () => { console.log('\x1b[32m%s\x1b[0m', `JSON Server is running at http://localhost:${port}/`); });
SSR Auth 中间件
import { RequestHandler } from 'express'; import { ofetch } from 'ofetch'; const auth: RequestHandler = async (req, res, next) => { console.log('SSR Entrypoing, AUTH', req.headers.cookie); const user = await ofetch('http://localhost:4400/me', { method: 'get', headers: { cookie: req.headers.cookie as any, }, credentials: 'include', }).catch(err => { console.log(err, '\njson-server has not found user from req.session'); }); if (!user) return next(); req.user = user; next(); }; export default auth;
问题排查与解决方案
1. 调整 express-session 中间件挂载范围与顺序
原代码将session中间件挂载在子app上,仅作用于该子app的路由,可能导致会话解析逻辑不统一。同时中间件顺序不合理,会影响会话解析优先级。
修改方案:
直接将session中间件挂载到主server实例,确保所有请求都经过会话解析,同时调整中间件顺序(session优先于cors、bodyParser):
dotenv.config(); const server = jsonServer.create(); const router = jsonServer.router('./json-server/db.json'); const middlewares = jsonServer.defaults(); // 1. 优先挂载session中间件,确保所有请求都经过会话解析 server.use(expressCookieSession); // 2. 配置带credentials的CORS,限制可信源 const corsOptions = { origin: `http://localhost:${process.env.VITE_SSR_PORT || 3300}`, credentials: true, }; server.use(cors(corsOptions)); // 3. 挂载body解析中间件 server.use(bodyParser.json()); // 4. 直接在主server上挂载auth路由,无需子app mockLogin(server, router); mockLogout(server); me(server); // 5. 最后挂载json-server默认中间件与路由 server.use(middlewares); server.use(router);
2. 优化 ofetch 的 Cookie 传递配置
服务器端使用ofetch时,credentials: 'include'是冗余配置(仅浏览器环境需要),且可能与手动设置的cookie头冲突,导致Cookie传递异常。
修改方案:
移除credentials配置,仅保留手动传递的cookie头:
const auth: RequestHandler = async (req, res, next) => { console.log('SSR Entrypoing, AUTH', req.headers.cookie); const user = await ofetch('http://localhost:4400/me', { method: 'get', headers: { cookie: req.headers.cookie as string, }, // 移除 credentials: 'include' }).catch(err => { console.log(err, '\njson-server 未从 req.session 找到用户'); }); if (!user) return next(); req.user = user; next(); };
3. 调整 express-session 的 Cookie 兼容性配置
sameSite: false在部分跨域场景下可能存在兼容问题,建议改为lax;同时设置明确的会话过期时间,避免会话无期限存在:
// mocked-auth/session.middleware.js const sessionConfig = { secret: 'your_secret_key', resave: false, saveUninitialized: true, cookie: { httpOnly: true, sameSite: 'lax', // 提升跨域场景兼容性 maxAge: 24 * 60 * 60 * 1000, // 设置24小时会话过期时间 }, };
验证步骤
- 重启json-server和SSR服务
- 登录后触发SSR页面请求,检查
/me接口的req.session是否包含user属性 - 确认SSR服务能正确获取用户信息
内容的提问来源于stack exchange,提问作者Alex Pappas
相关产品推荐
相关产品推荐

