跳到主要内容
编码转换2026-10-11Begin3 分钟阅读

JWT exp/nbf 时钟漂移:秒与毫秒、leeway,以及「刚签发就过期」

症状:签名对,时间不对

联调里一类高频 401:

  • JWT 解码器 里 Payload 清晰可读,HS256 校验也绿
  • 服务端仍报 Token expired、jwt expired,或更迷惑的 Token used before issued / nbf

这通常不是密钥错了,而是 NumericDate 语义或时钟出了问题。TLS 握手正常(证书另说)却鉴权失败时,优先走这条排查线。

RFC 7519 里的时间单位

标准声明 exp / iat / nbf 的类型是 NumericDate = 自 1970-01-01 UTC 起的秒数,不是 JavaScript 的毫秒。

量级粗判大约含义
10 位数(约 1e9)秒 —— 符合 RFC
13 位数(约 1e12)毫秒 —— 常见错误签发
16 位数及以上更像纳秒/雪花 ID,不是标准 NumericDate

签发方用 Date.now()(毫秒)直接写入 exp,验证方按秒比较,会得到「几十年后才过期」或完全乱跳的相对时间——取决于库如何夹逼。站内解码器对明显毫秒量级会做启发式归一,并在时间轴上显示 UTC +「2 小时前 / 3 天后」;生产校验库仍应以秒为准,并在签发侧修根因。

三个时钟陷阱

1. 分布式漂移

K8s / 多机房里几秒偏差很常见。验证时应设 leeway(时钟容忍)30–60 秒:now 落在 [nbf - leeway, exp + leeway] 才拒绝。leeway 不是让过期 token「多用一会儿」的产品功能,而是吸收 NTP 抖动;过大(数分钟)会放大重放窗口。

2. nbf 在未来

Token used before issued 往往来自:

  • 签发机时钟快于验证机
  • 或 nbf 被设成「整点生效」而流量提前几秒到达

先对两端跑 date -u,再查 NTP;不要先怀疑签名算法。

3. 只信前端的 exp

浏览器用本地时钟渲染「登录还剩多久」可以,但放行决策必须在服务端完成完整校验(签名 + exp/nbf + aud/iss + 吊销)。前端 exp 绿灯时,服务端可能已把 jti 拉黑。

和 alg=none、大整数 claims 一起看

  • alg=none:Header 声称不签名。历史库默认接受时,改 header、删签名即可绕过。解码页见红色警告属预期;生产必须算法白名单。详见JWT 解码指南。
  • 雪花 ID / 超大整数:sub 或业务字段若是 19 位整数,劣质 JSON.parse 会舍入。本站解码路径已做无损展示;你自己的服务端应用字符串或 BigInt 策略,别让 ID 在日志里被改错。

别和 HTTPS 证书搅在一起

层证明什么工具
TLS 证书连到的服务器身份 / 链是否完整证书解码器、证书链不完整排查
JWT调用方声称的用户/权限JWT 解码器、本文

握手失败(证书红屏)与 HTTP 401(令牌拒)同时出现时,先分开复现,再改配置——混修最浪费时间。

五分钟清单

  1. 解码看 exp/iat/nbf 位数:是秒还是毫秒?
  2. 时间轴相对偏移是否合理(刚签发却显示「已过期多年」→ 单位错)?
  3. 签发机与验证机 date -u 差几秒?leeway 是否 30–60s?
  4. Header alg 是否为 none 或被改成验证方未允许的算法?
  5. 401 时 TLS 是否其实已成功(证书问题另案)?

实现注记 / Implementer's note

站内时间轴把标准声明格式化为 UTC + 相对偏移,Copy 仍在工具栏上方,避免挡操作。毫秒归一是展示层启发式,不会替你的 API 改签发代码。HS256 校验在浏览器本地完成;RS256/JWKS 拉取未内置——开放平台场景请在服务端验签。与证书解码器相同的产品边界:材料不上传。

相关阅读

本文由 ToolVault 工具匣 提供。更多开发者工具请访问 首页。