You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 21:45:08