前后端跨子域部署下Session不持久、Cookie无法留存问题求助
问题描述
开发前后端分属不同子域的Web应用时,遭遇Session与Cookie相关异常,需确保Session正确保存至MySQL存储、Cookie跨请求正常留存。
环境配置
- 前端:React应用,运行地址
http://local.example.com:5173 - 后端:Express.js服务,基于Passport.js实现身份认证,运行地址
http://api.local.example.com:3003 - 会话管理:使用
express-session搭配MySQL session store
问题现象
- 用户登录后,后端未将Session数据写入MySQL会话存储
- 登录成功后浏览器网络面板可见
Set-Cookie头,但页面刷新后Cookie消失 - 再次登录时系统会创建全新的Session
补充细节
- 已配置CORS允许前端域名请求,且设置
credentials: true以确保请求携带Cookie - 请求受保护路由时,
req.user能正常获取,说明Passport.js认证逻辑正常 - 登录响应中存在
Set-Cookie头,但Cookie无法在后续请求中自动携带 - 移除CORS中间件后,Cookie能保存到会话存储,但前端后续请求不再携带Cookie
相关代码
app.js
const express = require("express"); const passport = require("passport"); const app = express(); const auth = require("./routes/auth"); const order = require("./routes/order"); const session = require("express-session"); const MySQLStore = require("express-mysql-session")(session); const env = require("dotenv").config(); const cors = require("cors"); app.use( cors({ origin: "http://app.local.example.com:5174", credentials: true, methods: ["GET", "POST", "PUT", "DELETE"], }) ); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use((req, res, next) => { console.log("Session ID from req:", req.sessionID); console.log("Session data:", req.user); next(); }); const options = { host: process.env.HOST, user: process.env.USER, password: process.env.PASSWORD, database: process.env.NAME, }; const sessionStore = new MySQLStore(options); sessionStore .onReady() .then(() => { console.log("MySQLStore ready"); }) .catch((error) => { console.error(error); }); app.use( session({ secret: process.env.SECRET, store: sessionStore, resave: false, saveUninitialized: true, cookie: { secure: false, maxAge: 1000 * 60 * 60 * 24, domain: ".local.example.com", }, }) ); require("./config/passportConfig"); app.use(passport.initialize()); app.use(passport.session()); app.use("/api/account", auth); app.use("/api/order", order); app.listen(3003, () => { console.log("Listening on port 3003..."); });
passport config
const passport = require("passport"); const LocalStrategy = require("passport-local").Strategy; const { getUserByEmail } = require("../models/user"); const { verifyPassword } = require("../services/passwordUtils"); const { getUserById } = require("../models/user"); async function verifyUser(email, password, done) { try { const user = await getUserByEmail(email); if (user) { const isPasswordVerified = await verifyPassword(password, user.pass); if (isPasswordVerified) { return done(null, user); } return done(null, false, { message: "Password is incorrect" }); } return done(null, false, { message: `User ${email} does not exist` }); } catch (err) { return done(err); } } const strategy = new LocalStrategy( { usernameField: "email", passwordField: "pass", }, verifyUser ); passport.use(strategy); passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser(async (userId, done) => { try { const user = await getUserById(userId); return done(null, user); } catch (err) { done(err); } });
Login.jsx
export default function LoginForm() { const [formData, setFormData] = useState({ email: "", pass: "", }); const [error, setError] = useState(false); function handleChange(event) { const { value, name } = event.target; setFormData((prev) => { return { ...prev, [name]: value, }; }); } async function validateLogin() { try { const data = await fetch( "http://api.local.example.com:3003/api/account/login", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(formData), credentials: "include", } ); console.log("hi"); return data; } catch (err) { return err; } } }
解决方案
1. 修正CORS配置的origin值
你的前端实际运行地址是http://local.example.com:5173,但当前CORS配置的origin为http://app.local.example.com:5174,地址不匹配会导致浏览器拒绝保存Cookie。修改CORS配置:
app.use( cors({ origin: "http://local.example.com:5173", credentials: true, methods: ["GET", "POST", "PUT", "DELETE"], }) );
2. 配置Cookie的SameSite属性
跨子域场景下,需明确设置Cookie的SameSite属性,避免浏览器限制Cookie发送。在express-session的cookie配置中添加:
cookie: { secure: false, // 本地开发用false,生产HTTPS环境需设为true maxAge: 1000 * 60 * 60 * 24, domain: ".local.example.com", sameSite: "Lax", // 跨域场景推荐用"Lax",若需跨站第三方请求可设为"None"(需配合secure: true) }
3. 手动触发Session保存
Passport.js登录后修改req.session,但部分场景下需手动触发保存操作。在登录路由处理逻辑中添加:
// 示例登录路由处理 router.post('/login', passport.authenticate('local'), (req, res) => { req.session.save(err => { if (err) return res.status(500).send('Session保存失败'); res.send('登录成功'); }); });
4. 检查MySQL权限与会话表
确保MySQL用户拥有会话表(默认表名sessions)的读写权限,可手动查询表数据确认Session是否正常写入。
5. 统一前端请求的credentials配置
封装全局fetch工具函数,确保所有需携带Cookie的请求都设置credentials: 'include':
async function apiFetch(url, options = {}) { return fetch(url, { ...options, credentials: 'include', headers: { 'Content-Type': 'application/json', ...options.headers, }, }); }
内容的提问来源于stack exchange,提问作者brotherG
相关产品推荐
相关产品推荐

