Node.js中使用Passport实现新登录时注销旧会话
解决Passport新登录时自动注销其他设备旧会话的方案
要实现用户新登录时自动注销其他设备的旧会话,需要完成以下几个核心步骤:
1. 修改用户Schema,添加有效会话存储字段
在你的MongoDB User Schema中新增activeSessions数组,用于存储当前用户的有效会话ID:
let UserSchema = new Schema({ username: String, email : String, password: String, stream_key : String, activeSessions: [String] // 新增:存储用户当前有效的会话ID列表 });
2. 优化Passport序列化/反序列化逻辑
将serializeUser改为仅存储用户ID(减少会话存储体积,也方便后续会话关联),deserializeUser通过ID查询用户:
// 修改Passport配置中的serializeUser和deserializeUser passport.serializeUser( (user, cb) => { cb(null, user._id); // 只存用户ID }); passport.deserializeUser( async (id, cb) => { try { const user = await User.findById(id); cb(null, user); } catch (err) { cb(err); } });
3. 确保会话存储到MongoDB
如果还没配置会话持久化,使用connect-mongo将express-session存储到MongoDB(否则会话仅存内存,重启服务后丢失):
// 在app.js中添加会话配置(替换你的连接字符串和密钥) const session = require('express-session'); const MongoStore = require('connect-mongo'); app.use(session({ secret: 'your-strong-secret-key', resave: false, saveUninitialized: false, store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/your-db-name' }), cookie: { secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS时设为true maxAge: 24 * 60 * 60 * 1000 // 会话有效期1天,按需调整 } }));
4. 修改登录逻辑,清除旧会话并保存新会话
将登录路由的passport.authenticate改为自定义回调,在用户登录成功后:
- 删除该用户所有旧会话
- 清空用户的
activeSessions并添加当前新会话ID - 完成用户登录
修改后的登录POST路由:
router.post('/', (req, res, next) => { passport.authenticate('localLogin', async (err, user, info) => { if (err) return next(err); if (!user) { req.flash('email', info?.email || ''); req.flash('password', info?.password || ''); return res.redirect('/login'); } const sessionStore = req.sessionStore; // 1. 查找并删除该用户的所有旧会话 const oldSessions = await sessionStore.collection('sessions').find({ 'passport.user': user._id.toString() }).toArray(); for (const sess of oldSessions) { await sessionStore.destroy(sess._id); } // 2. 更新用户的有效会话列表,只保留当前新会话 user.activeSessions = [req.session.id]; await user.save(); // 3. 完成用户登录 req.logIn(user, (loginErr) => { if (loginErr) return next(loginErr); return res.redirect('/'); }); })(req, res, next); });
5. 添加会话验证中间件,拦截无效会话
新增中间件,每次请求时检查当前会话ID是否在用户的activeSessions列表中,不在则强制注销:
// 在app.js中定义中间件 function validateActiveSession(req, res, next) { if (req.isAuthenticated()) { // 检查当前会话是否在用户的有效列表中 if (!req.user.activeSessions.includes(req.session.id)) { req.logout((logoutErr) => { if (logoutErr) return next(logoutErr); res.redirect('/login'); }); } else { next(); } } else { next(); } } // 将中间件应用到所有需要验证登录的路由 app.use(validateActiveSession);
6. 优化注销逻辑(可选)
如果有注销路由,确保注销时移除当前会话ID并删除会话存储:
router.get('/logout', async (req, res, next) => { if (req.isAuthenticated()) { // 从用户有效会话列表中移除当前会话 req.user.activeSessions = req.user.activeSessions.filter(id => id !== req.session.id); await req.user.save(); // 删除会话存储中的当前会话 await req.sessionStore.destroy(req.session.id); } req.logout((err) => { if (err) return next(err); res.redirect('/login'); }); });
关键说明
之前手动删除会话后旧会话仍生效的原因:Passport的deserializeUser默认不会验证会话的有效性,只要客户端携带的cookie对应会话ID存在(或曾存在),就会返回用户信息。通过上述步骤,我们通过activeSessions列表和验证中间件,确保只有用户当前认可的会话才能保持登录状态。
内容的提问来源于stack exchange,提问作者Ninja-aman
相关产品推荐
相关产品推荐

