jwt.verify() 是否过期由是否抛出 tokenexpirederror 错误决定,需用 try/catch 捕获;它自动校验 exp/nbf,不依赖返回值或手动比对时间戳。

如何判断 jwt.verify() 返回的令牌是否已过期
MongoDB 本身不处理 JWT 验证,它只存数据;验证逻辑在应用层(如 Node.js)完成。真正决定令牌是否过期的是 jwt.verify() 调用时是否抛出 TokenExpiredError 错误。这个错误不是靠手动比对 exp 字段时间戳来的——Node.js 的 jsonwebtoken 库会在验证签名后自动检查 exp 和 nbf,失败就直接 throw。
常见错误现象:调用 jwt.verify(token, secret) 后没加 try/catch,导致未捕获的异常崩掉服务;或者捕获了但只检查 err.name === 'TokenExpiredError' 却忽略 err.name === 'JsonWebTokenError'(比如签名无效或格式错),误判为“过期”。
- 必须用
try/catch包裹jwt.verify(),不能依赖返回值真假判断 -
exp字段单位是秒(Unix timestamp),不是毫秒;若你用Date.now()手动比对,记得除以 1000 - 生产环境建议传入
{ clockTolerance: 5 }参数,容忍服务间最多 5 秒时钟偏差,避免误判过期
为什么 MongoDB 不该存储 session 过期时间
Session 寿命控制不在数据库侧,而是在生成令牌或创建 session 时由应用层决定。把 expiresAt 字段写进 MongoDB 的 sessions 集合看似直观,实则引入严重问题:每次请求都要查库比对时间,失去无状态优势;且无法自动清理过期 session,除非配定时 job 或 TTL 索引——但 TTL 索引只支持日期字段,且延迟可能达 60 秒。
更合理的方式是:使用 JWT 自带 exp,或用 Redis 存 session 并设 EXPIRE;MongoDB 只用于存用户元数据(如 users 集合),不承担 session 管理职责。
- 如果硬要用 MongoDB 存 session,必须给
expiresAt字段建 TTL 索引:db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }) - TTL 索引删除是后台异步任务,不保证实时;不能依赖它做“即时登出”逻辑
- JWT 场景下,
exp是唯一可信过期依据;MongoDB 里存的任何时间字段都只是冗余备份,不可作为决策源
如何设置 Express + JWT 的实际超时行为
前端发来的 token 是否有效,取决于后端验证逻辑;但“用户多久没操作就强制登出”,这是客户端与服务端协同的结果。单纯改 exp 值只能控制单次 token 寿命,无法实现“30 分钟无操作自动失效”的交互式 session 行为。
真实场景需要两层控制:短生命周期 access token(如 15 分钟) + 长生命周期 refresh token(如 7 天),配合前端定时刷新机制。MongoDB 在这里只可能存 refresh token 的哈希值和绑定设备信息,用于吊销。
- access token 的
exp设太长(如 24h)会放大泄露风险;设太短(如 1min)则频繁刷新影响体验 - refresh token 必须存服务端(如 MongoDB 的
refresh_tokens集合),字段至少含:hashedToken、userId、expiresAt、revoked - 每次用 refresh token 换新 access token 时,要校验
revoked === false且expiresAt > new Date(),然后立即让旧 refresh token 失效(可选滚动更新)
容易被忽略的时区与精度陷阱
所有时间判断都基于服务器本地时间,但 exp 是 UTC 时间戳。如果你在代码里用 new Date().getTime() / 1000 做比对,没问题;但若用 new Date().toISOString() 或 moment().format('YYYY-MM-DD HH:mm:ss') 就可能因时区偏移出错。
另一个隐蔽问题是 Node.js 的 process.uptime() 或某些监控埋点用毫秒计时,而 JWT 的 exp 是秒级——混用会导致“刚签发就报过期”。
- 始终用
Math.floor(Date.now() / 1000)获取当前秒级时间戳,与exp对齐 - 不要在 MongoDB 查询中用
$lt: new Date()直接比expiresAt字段,除非确认该字段存的是ISODate类型且时区一致 - 测试时别用系统时间快进模拟过期,应改代码注入固定时间戳做单元测试,否则 CI 环境容易 flaky











