Flutter+NodeJS抽奖功能:如何验证用户的最新JWT令牌?
解决方案:无需数据库验证最新中奖令牌
针对你的需求,以下几个轻量方案无需依赖独立数据库,就能在Node.js后端便捷验证中奖令牌是否为最新版本:
方案1:JWT嵌入抽奖批次号 + 内存存储当前有效批次号
每次抽奖生成唯一的批次号(比如时间戳、递增数字),将其存入Node.js内存,同时把批次号写入中奖用户的JWT中。验证时只需对比JWT中的批次号与内存中的当前有效批次号即可。
代码示例
const jwt = require('jsonwebtoken'); // 内存存储当前有效抽奖批次号,启动时初始化 let currentValidBatchId = null; // 抽奖逻辑:生成批次号并发放JWT function drawWinner(winnerId) { // 用时间戳生成唯一批次号 const newBatchId = Date.now().toString(); currentValidBatchId = newBatchId; // 生成包含批次号的JWT,设置权益有效期(示例7天) const token = jwt.sign( { userId: winnerId, batchId: newBatchId }, 'your-strong-secret-key', { expiresIn: '7d' } ); return token; } // 令牌验证中间件 function validateWinnerToken(req, res, next) { const token = req.headers['authorization']?.split(' ')[1]; if (!token) return res.status(401).send('未提供令牌'); try { const decoded = jwt.verify(token, 'your-strong-secret-key'); // 校验批次号是否为当前有效版本 if (decoded.batchId !== currentValidBatchId) { return res.status(403).send('令牌无效或已过期'); } req.winnerInfo = decoded; next(); } catch (err) { return res.status(403).send('令牌无效'); } }
优势
- 完全无需数据库,验证逻辑极简,性能损耗可忽略
- 批次号更新仅需覆盖内存值,操作成本极低
方案2:JWT的jti声明 + 内存黑名单
利用JWT的jti(JWT唯一ID)字段,每次抽奖生成新的jti,并将上一批次的jti加入内存黑名单。验证时检查令牌的jti是否在黑名单中,或是否与最新有效jti一致。
代码示例
const jwt = require('jsonwebtoken'); const crypto = require('crypto'); const LRU = require('lru-cache'); // 用LRU缓存维护黑名单,限制存储数量并自动过期(与JWT有效期一致) const invalidJtiCache = new LRU({ max: 100, ttl: 7 * 24 * 60 * 60 * 1000 // 7天过期 }); let latestValidJti = null; function drawWinner(winnerId) { const newJti = crypto.randomUUID(); // 将上一批次的有效jti加入黑名单 if (latestValidJti) { invalidJtiCache.set(latestValidJti, true); } latestValidJti = newJti; const token = jwt.sign( { userId: winnerId, jti: newJti }, 'your-strong-secret-key', { expiresIn: '7d' } ); return token; } function validateWinnerToken(req, res, next) { const token = req.headers['authorization']?.split(' ')[1]; if (!token) return res.status(401).send('未提供令牌'); try { const decoded = jwt.verify(token, 'your-strong-secret-key'); // 校验jti是否在黑名单,或是否为最新有效jti if (invalidJtiCache.has(decoded.jti) || decoded.jti !== latestValidJti) { return res.status(403).send('令牌无效或已过期'); } req.winnerInfo = decoded; next(); } catch (err) { return res.status(403).send('令牌无效'); } }
优势
- 黑名单仅存储旧批次的
jti,内存占用极小 - 结合LRU缓存自动清理过期条目,无需手动维护
方案3:内存存储最新令牌哈希值
每次生成新的中奖令牌后,将令牌的哈希值(如SHA256)存入内存。验证时计算收到令牌的哈希值,与内存中的哈希对比一致则有效。
代码示例
const jwt = require('jsonwebtoken'); const crypto = require('crypto'); let latestTokenHash = null; function drawWinner(winnerId) { const token = jwt.sign( { userId: winnerId }, 'your-strong-secret-key', { expiresIn: '7d' } ); // 生成令牌哈希存入内存 latestTokenHash = crypto.createHash('sha256').update(token).digest('hex'); return token; } function validateWinnerToken(req, res, next) { const token = req.headers['authorization']?.split(' ')[1]; if (!token) return res.status(401).send('未提供令牌'); try { // 先验证JWT签名防止伪造 const decoded = jwt.verify(token, 'your-strong-secret-key'); // 对比哈希值判断是否为最新令牌 const tokenHash = crypto.createHash('sha256').update(token).digest('hex'); if (tokenHash !== latestTokenHash) { return res.status(403).send('令牌无效或已过期'); } req.winnerInfo = decoded; next(); } catch (err) { return res.status(403).send('令牌无效'); } }
优势
- 逻辑最简单,无需解析JWT内容即可快速完成哈希对比
- 内存仅存储一个哈希值,资源占用极低
注意事项
- 内存持久化问题:如果Node.js进程重启,内存中的批次号/
jti/哈希会丢失。若需持久化,可选用轻量内存数据库(如Redis);若重启后可重新触发抽奖或标记期间权益无效,纯内存方案完全可行。 - JWT安全:务必使用强签名密钥,且通过HTTPS传输令牌,防止伪造或拦截。
- 过期时间:建议将JWT过期时间设置为与权益有效期一致,即使令牌未被替换,过期后也会自动失效,降低安全风险。
内容的提问来源于stack exchange,提问作者Pop Dorian
相关产品推荐
相关产品推荐

