如何提前失效JWT access_token?Node.js生成新token时撤销旧token
在Node.js中实现生成新JWT时立即失效旧Token的方案
JWT本身是无状态的,一旦签发就无法直接撤销,所以需要通过额外的机制实现旧Token的即时失效。以下是几种实用的实现方式:
1. 令牌黑名单(最常用方案)
维护一个存储已失效Token的黑名单,每次验证Token时先检查是否在黑名单内。推荐用Redis这类支持过期时间的缓存存储,这样可以自动清理过期的黑名单条目(和原Token有效期一致),无需手动维护。
实现步骤:
- 安装依赖:
npm install jsonwebtoken ioredis - 初始化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
相关产品推荐
相关产品推荐

