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

如何提前失效JWT access_token?Node.js生成新token时撤销旧token

在Node.js中实现生成新JWT时立即失效旧Token的方案

JWT本身是无状态的,一旦签发就无法直接撤销,所以需要通过额外的机制实现旧Token的即时失效。以下是几种实用的实现方式:

1. 令牌黑名单(最常用方案)

维护一个存储已失效Token的黑名单,每次验证Token时先检查是否在黑名单内。推荐用Redis这类支持过期时间的缓存存储,这样可以自动清理过期的黑名单条目(和原Token有效期一致),无需手动维护。

实现步骤:

  1. 安装依赖:npm install jsonwebtoken ioredis
  2. 初始化Redis客户端并编写核心逻辑:
const jwt = require('jsonwebtoken');
const Redis = require('ioredis');
const redis = new Redis(); // 根据你的Redis配置修改参数

// 生成新Token并拉黑旧Token
async function generateNewToken(userId, oldToken) {
  // 将旧Token加入黑名单,过期时间与原Token剩余有效期一致
  if (oldToken) {
    try {
      const decoded = jwt.decode(oldToken);
      const remainingTime = decoded.exp - Math.floor(Date.now() / 1000);
      if (remainingTime > 0) {
        await redis.setex(`blacklist:${oldToken}`, remainingTime, 'invalid');
      }
    } catch (err) {
      // 旧Token无效则直接忽略
      console.error('处理旧Token失败:', err);
    }
  }

  // 生成新的5天有效期Token
  const newToken = jwt.sign({ userId }, 'your-secret-key', { expiresIn: '5d' });
  return newToken;
}

// Token验证中间件
async function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('无有效访问凭证');

  // 先检查黑名单
  const isBlacklisted = await redis.exists(`blacklist:${token}`);
  if (isBlacklisted) return res.status(401).send('该凭证已失效');

  // 验证JWT签名与有效期
  try {
    const decoded = jwt.verify(token, 'your-secret-key');
    req.userId = decoded.userId;
    next();
  } catch (err) {
    return res.status(401).send('凭证无效');
  }
}

2. 令牌版本号机制

给每个用户维护一个Token版本号,生成新Token时递增该版本号,验证Token时对比版本号是否一致。版本号可以存在你的用户数据库(如MongoDB、MySQL)中。

实现步骤:

假设使用MongoDB,用户模型包含tokenVersion字段(默认值为0):

const jwt = require('jsonwebtoken');
const User = require('./models/User'); // 你的用户模型

// 生成新Token并更新版本号
async function generateNewToken(userId) {
  // 递增用户的Token版本号
  const updatedUser = await User.findByIdAndUpdate(
    userId,
    { $inc: { tokenVersion: 1 } },
    { new: true }
  );
  if (!updatedUser) throw new Error('用户不存在');

  // 生成携带版本号的新Token
  const newToken = jwt.sign(
    { userId, version: updatedUser.tokenVersion },
    'your-secret-key',
    { expiresIn: '5d' }
  );
  return newToken;
}

// Token验证中间件
async function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('无有效访问凭证');

  try {
    const decoded = jwt.verify(token, 'your-secret-key');
    const user = await User.findById(decoded.userId);

    // 对比版本号,不一致则Token失效
    if (!user || user.tokenVersion !== decoded.version) {
      return res.status(401).send('该凭证已失效');
    }

    req.userId = decoded.userId;
    next();
  } catch (err) {
    return res.status(401).send('凭证无效');
  }
}

3. 结合RefreshToken优化(推荐生产环境使用)

如果业务允许,可缩短AccessToken的有效期(比如15分钟),配合RefreshToken获取新的AccessToken。当需要生成新AccessToken时,用RefreshToken换取并同时将旧AccessToken加入黑名单。这种方式能降低长期有效Token的安全风险,同时满足即时失效的需求。

方案对比

  • 黑名单:对现有代码改动小,性能较好,但需要额外的缓存服务;
  • 版本号:无需额外依赖,用现有数据库即可,但每次验证需查询数据库,性能略低;
  • RefreshToken结合:安全性更高,适合生产环境,建议与黑名单机制配合使用。

内容的提问来源于stack exchange,提问作者Ankit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 09:42:44