Express.js每次请求创建多会话问题排查求助
看起来你遇到的核心问题是跨域场景下会话Cookie没有被正确识别,导致每次前端请求都触发新会话创建,结合你的配置和现象,我给你梳理几个排查方向和修复步骤:
一、先确认前端请求的Cookie携带逻辑
虽然你提到没发现请求不带Cookie,但跨域场景下浏览器的Cookie携带是有严格限制的,先检查这几个关键点:
前端必须显式开启跨域携带Cookie:
如果你用Axios,要在请求里加withCredentials: true:axios.post('你的GraphQL接口地址', query, { withCredentials: true })用Fetch的话,要设置
credentials: 'include':fetch('你的GraphQL接口地址', { method: 'POST', credentials: 'include', body: JSON.stringify(query) })打开浏览器开发者工具的Network面板,查看每个GraphQL请求和OPTIONS预检请求的Request Headers,确认是否存在
Cookie字段,且携带的会话ID和Redis里的初始会话ID一致。Cookie的跨域配置是否匹配:
本地开发环境下(HTTP协议):sameSite要设为lax(不能是strict,否则跨域跳转后Cookie不会被携带)secure必须设为false(HTTP环境下secure: true的Cookie会被浏览器忽略)domain设为localhost(如果前后端都是localhost不同端口的话)
生产环境HTTPS下,如果是跨域名部署,sameSite要设为none,同时secure: true,domain设为根域名(比如.example.com)实现子域名共享。
二、排查会话配置的潜在问题
你的会话配置里有几个可能的风险点:
disableTTL: true是隐患:
这个配置会让Redis里的会话永久存在,即使找到问题根源,之前的无效会话也会一直占用空间。建议先去掉这个配置,设置和cookie.maxAge一致的TTL,让Redis自动清理过期会话:const sessionOptions = { store: new RedisStore({ client: redisClient, ttl: toTime(config.cookie.maxAge).ms() / 1000, // 转成秒单位 }), // 其他配置不变 };确保OPTIONS预检请求不触发会话创建:
虽然你设置了saveUninitialized: false,但某些中间件(比如Passport的钩子)可能会无意中修改会话对象,导致空会话被保存到Redis。可以在会话中间件之前加一个过滤逻辑,跳过OPTIONS请求的会话处理:// 先处理OPTIONS请求,再初始化会话 app.use((req, res, next) => { if (req.method === 'OPTIONS') { return next(); } session(sessionOptions)(req, res, next); }); // 再初始化Passport app.use(passport.initialize()); app.use(passport.session());
三、关于自定义genid的奇怪现象
你给认证服务设置的genid逻辑(无username时返回null)本身就不符合express-session的要求——genid必须返回有效的会话ID字符串,返回null会导致会话无法正常创建,自然触发CSRF异常。而登录成功后只生成一个会话,是因为此时username存在,生成了固定前缀的会话ID,前端请求恰好携带了这个Cookie,属于歪打正着,不是正确的解决方案,建议恢复默认的genid逻辑。
四、验证修复的步骤
- 清空Redis所有会话:执行
FLUSHALL命令 - 重启所有微服务
- 登录后检查Redis,确认只有1个会话
- 跳转到React前端,发起GraphQL请求,观察会话数量是否保持不变
- 点击页面链接,确认会话数量不再持续增加
如果还是有问题,建议用浏览器Application面板查看Cookie的属性,确认domain、sameSite、secure是否和配置一致,再逐一排查Passport的serializeUser/deserializeUser逻辑,看是否存在每次请求都修改会话对象的情况。
内容的提问来源于stack exchange,提问作者Chris

