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

如何使用Node.js与MongoDB实现用户登出功能?

JWT 登出功能实现思路与代码示例

由于 JWT 是无状态的,一旦签发在过期前默认始终有效,无法直接主动撤销,因此登出功能需要通过服务端的额外验证机制来拦截无效 Token。以下是两种实用的实现方案:

方案一:Token 黑名单机制(适合中小规模应用)

核心思路是维护一个已注销 Token 的黑名单,认证时先检查 Token 是否在黑名单内,再进行常规验证。

步骤1:准备存储(以 Redis 为例)

Redis 自带过期时间机制,适合存储临时的黑名单数据,无需手动清理过期条目。

npm install redis

初始化 Redis 客户端:

const redis = require('redis');
const redisClient = redis.createClient({
  url: process.env.REDIS_URL // 替换为你的 Redis 地址
});
redisClient.connect();

步骤2:编写登出接口

接收当前用户的 Token,将其存入黑名单并设置与原 Token 一致的过期时长:

router.post("/admin/sign_out", authenticate, async (req, res) => {
  try {
    const token = req.body.token || req.query.token || req.headers["autherization"];
    const decoded = jwt.verify(token, process.env.TOKEN_KEY);
    // 计算剩余过期时间(秒)
    const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);

    // 将 Token 加入黑名单,自动过期
    await redisClient.setEx(token, expiresIn, 'invalidated');
    
    return res.status(200).send("登出成功");
  } catch (err) {
    return res.status(400).send("登出失败");
  }
});

步骤3:修改认证中间件

新增黑名单检查逻辑,拦截已注销的 Token:

const authenticate = async (req, res, next) => {
  const token = req.body.token || req.query.token || req.headers["autherization"];

  if (!token) {
    return res.status(403).send("需要 Token 进行认证");
  }

  // 先检查 Token 是否在黑名单中
  const isBlacklisted = await redisClient.get(token);
  if (isBlacklisted) {
    return res.status(401).send("Token 已失效(已登出)");
  }

  try {
    const decoded = jwt.verify(token, config.TOKEN_KEY);
    req.user = decoded;
  } catch (err) {
    return res.status(401).send("无效 Token");
  }
  return next();
};

方案二:数据库存储有效 Token(适合多设备管理场景)

核心思路是在用户文档中维护一个「有效 Token 列表」,登录时添加新 Token,登出时移除对应 Token,认证时验证 Token 是否在列表内。

步骤1:修改 User Schema

添加存储有效 Token 的数组字段:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  activeTokens: [{ type: String }] // 新增:存储当前有效的 Token
});

module.exports = mongoose.model('User', userSchema);

步骤2:修改登录接口

生成 Token 后将其存入用户的 activeTokens 数组:

router.post("/admin/sign_in", async (req, res) => {
  try {
    const { email, password } = req.body;

    if (!(email && password)) {
      return res.status(400).send("所有字段均为必填");
    }
    const user = await User.findOne({ email });

    if (user && (await bcrypt.compare(password, user.password))) {
      const token = jwt.sign(
        { user_id: user._id, email },
        process.env.TOKEN_KEY,
        { expiresIn: "15h" }
      );

      // 将新 Token 添加到有效列表并保存
      user.activeTokens.push(token);
      await user.save();

      return res.status(200).json(user);
    }
    return res.status(400).send("无效的账号或密码");
  } catch (err) {
    return res.status(400).send("无效的账号或密码");
  }
});

步骤3:修改认证中间件

验证 Token 有效性的同时,检查其是否在用户的有效 Token 列表中:

const authenticate = async (req, res, next) => {
  const token = req.body.token || req.query.token || req.headers["autherization"];

  if (!token) {
    return res.status(403).send("需要 Token 进行认证");
  }

  try {
    const decoded = jwt.verify(token, config.TOKEN_KEY);
    const user = await User.findById(decoded.user_id);

    // 检查 Token 是否在有效列表内
    if (!user.activeTokens.includes(token)) {
      return res.status(401).send("Token 已失效(已登出)");
    }

    req.user = decoded;
  } catch (err) {
    return res.status(401).send("无效 Token");
  }
  return next();
};

步骤4:编写登出接口

从用户的有效 Token 列表中移除当前 Token:

router.post("/admin/sign_out", authenticate, async (req, res) => {
  try {
    const token = req.body.token || req.query.token || req.headers["autherization"];
    const decoded = jwt.verify(token, process.env.TOKEN_KEY);

    // 从用户的有效列表中移除 Token
    await User.findByIdAndUpdate(
      decoded.user_id,
      { $pull: { activeTokens: token } },
      { new: true }
    );

    return res.status(200).send("登出成功");
  } catch (err) {
    return res.status(400).send("登出失败");
  }
});

额外注意事项

  • 前端登出时,需同步删除本地存储的 Token(如 localStorage、sessionStorage 或 Cookie),避免后续请求携带无效 Token。
  • 若使用 MongoDB 存储黑名单或有效 Token,需定期清理过期的无效 Token,可通过定时任务实现。
  • 多设备场景下,数据库方案支持用户查看所有登录设备并选择性登出某一设备,灵活性更高。

内容的提问来源于stack exchange,提问作者sumanth.js

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 09:10:44