Express-Session多用户会话异常:后续用户无法生成独立会话求助
多会话管理问题:后续用户登录无法创建独立会话
我正在开发用户认证功能,将JWT令牌存储在MongoDB的session集合中,但遇到多会话管理问题:第一个用户登录成功后,后续用户无法生成并存储独立会话。
测试步骤:
- 使用Thunder Client和MongoDB Compass测试
- 第一个用户登录凭据:
{ "email": "mile@email.com", "password": "asdfAsdf1!" }
会话成功创建并存入session集合,生成connect.sid cookie。
- 第二个用户登录凭据:
{ "email": "john@email.com", "password": "asdfAsdf2!" }
此时第一个用户的会话ID被重新生成,而非创建全新独立会话,之前的会话也不会保留,connect.sid cookie保持不变,始终只有一个会话对象。
相关代码
会话配置
app.use( session({ secret: "key_that_will_sign_the_cookie", resave: false, saveUninitialized: false, store: MongoStore.create({ mongoUrl: "mongodb://127.0.0.1:27017/addressBook", collectionName: "sessions", }), cookie: { secure: false, maxAge: 15 * 60 * 1000, }, }) );
userController.ts
export const logIn = asyncHandler(async (req: Request, res: Response) => { const { email, password } = req.body; const user = await userService.login(email, password); const token = tokenService.generateToken( user._id, "ACCESS_TOKEN_PRIVATE_KEY_HERE" ); // Set session (req.session as MySession).jwtToken = token; res.status(200).json({ success: true, message: "You have successfully logged in.", }); }); export const logout = asyncHandler(async (req: Request, res: Response) => { req.session.destroy((err) => { if (err) { console.log("Error destroying session", err.message); res.status(500).json({ success: false, message: "Error logging out.", }); } else { res.clearCookie("connect.sid"); res .status(200) .json({ success: true, message: "Successfully logged out." }); } }); });
requireAuth.ts(中间件)
const requireAuth = async (req: Request, res: Response, next: NextFunction) => { try { const { jwtToken } = (req.session as MySession) || {}; if (!jwtToken) { return res.status(401).json({ messagE: messages.unauthorized }); } const { sub: userId } = jwt.verify( jwtToken, "ACCESS_TOKEN_PRIVATE_KEY_HERE" ); const user = await User.findOne({ _id: userId }); if (!user) { return res.status(401).json({ messagE: messages.unauthorized }); } req.user = user; next(); } catch (error: any) { return res.status(401).json({ message: error.message }); } };
更新说明
使用session是为了代码分层,将业务逻辑拆分到不同文件。我使用jsonwebtoken生成认证令牌并存储在session中,后续通过requireAuth中间件验证JWT有效性,用于保护所有联系人路由:
router.use(requireAuth); router .get("/", getAllContacts) .get("/favorite", getFavoriteContacts) .get("/:contactId", getContactById) .post("/", addNewContact) .patch("/:contactId", updateContact) .delete("/:contactId", deleteContact);
tokenService.ts
import jwt from "jsonwebtoken"; import mongoose from "mongoose"; export const generateToken = ( userId: mongoose.Types.ObjectId, secret: string ): string => { const payload = { sub: userId, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 15 * 60, }; return jwt.sign(payload, secret); }; export default { generateToken };
userService.ts
const login = async (email: string, password: string): Promise<IUserDoc> => { loginValidation(email, password); const user = await getUserByEmail(email); const isValidUser = user && (await user.isPasswordMatch(password)); if (!isValidUser) { throw new ApiError(400, messages.incorrectCredentials); } return user; };
问题原因与解决方案
问题根源
核心问题有两个:
- 测试场景限制:使用同一个客户端(Thunder Client)发起请求时,客户端会自动复用已有的
connect.sidcookie,导致第二个用户的登录请求携带第一个用户的会话标识,后端直接更新该会话内容而非创建新会话。 - 登录逻辑未处理多会话场景:express-session默认基于请求携带的cookie匹配已有会话,仅当请求无会话cookie时才会创建新会话,现有代码未在登录时强制生成新会话。
解决方案
1. 模拟多客户端测试
在Thunder Client中测试第二个用户登录时,需:
- 打开新的请求标签页,或在当前标签页的「Cookie」选项卡删除
connect.sid - 或使用不同浏览器/隐私窗口发起第二个用户的登录请求
以此模拟不同用户的独立会话环境,验证多会话创建逻辑。
2. 登录时强制生成新会话
若需在用户登录时,无论当前是否存在会话,都为新用户创建独立会话,可修改userController.ts的logIn函数:
export const logIn = asyncHandler(async (req: Request, res: Response) => { const { email, password } = req.body; const user = await userService.login(email, password); const token = tokenService.generateToken( user._id, "ACCESS_TOKEN_PRIVATE_KEY_HERE" ); // 先销毁已有会话(如果存在) await new Promise<void>((resolve, reject) => { req.session.destroy((err) => { if (err) reject(err); else resolve(); }); }); // 重新生成新会话 req.session.regenerate((err) => { if (err) { return res.status(500).json({ success: false, message: "Failed to create session." }); } // 存储新JWT令牌到新会话 (req.session as MySession).jwtToken = token; res.status(200).json({ success: true, message: "You have successfully logged in.", }); }); });
3. 优化会话配置(增强安全性与隔离性)
调整会话配置,提升多会话管理的安全性:
app.use( session({ secret: "key_that_will_sign_the_cookie", resave: false, saveUninitialized: false, name: "user_session", // 自定义cookie名称,避免默认值冲突 store: MongoStore.create({ mongoUrl: "mongodb://127.0.0.1:27017/addressBook", collectionName: "sessions", }), cookie: { secure: process.env.NODE_ENV === "production", // 生产环境启用HTTPS传输 maxAge: 15 * 60 * 1000, httpOnly: true, // 禁止前端JS访问cookie,防范XSS sameSite: "strict", // 限制跨站cookie传输,防范CSRF }, }) );
验证方法
完成修改后:
- 第一个用户登录,MongoDB的
sessions集合新增一条会话记录,客户端获取到对应cookie - 清除客户端cookie或用新窗口登录第二个用户,MongoDB新增第二条独立会话记录
- 分别用两个用户的cookie请求受保护路由,验证权限隔离是否正常
内容的提问来源于stack exchange,提问作者user5618326
相关产品推荐
相关产品推荐

