错误信息
"nbf" claim timestamp check failed
jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED
含义
令牌带有 `nbf`(“not before”)声明,按验证方的时钟,这个时间仍在未来,因此 jose 以“尚未生效”为由拒绝该令牌。签名和其他声明可能完全没有问题;这个错误只与时间有关。它几乎总是意味着颁发者和验证方的时钟相差超过了你允许的容差(默认为零)。
原因
验证方的时钟慢于颁发者的时钟
常见许多颁发者把 `nbf` 设为签发时间。只要验证主机的时钟慢了哪怕几秒,每个刚签发的令牌在这段时间内都是“来自未来”的。容器和虚拟机继承宿主机的时钟,因此一台时钟漂移的宿主机会让其上的所有服务同时出问题。
令牌一签发就被使用,且没有设置容差
常见测试和服务间调用常常在签发令牌后几毫秒内就把它发送出去。`clockTolerance` 为默认的 0 秒时,两台机器之间正常的时钟偏差就足以导致间歇性失败。
颁发者有意把 nbf 设在未来
偶见有些系统会预先签发稍后才生效的令牌(定时任务、延迟生效的链接)。提前使用这类令牌时,拒绝它是正确的结果——调用方必须等到 `nbf` 指定的时间。
nbf 写成了毫秒值
罕见JWT 中的时间是自纪元(epoch)以来的秒数(RFC 7519,NumericDate)。写入 `Date.now()` 的颁发者会生成约 50,000 年后的 `nbf`,令牌永远不会生效。
修复方法
- 1.
显式允许少量时钟偏差
`clockTolerance` 接受秒数或时长字符串。30~60 秒足以吸收实际环境中的时钟漂移;它不是用来提前几分钟接受令牌的手段。
js import { jwtVerify } from "jose"; const { payload } = await jwtVerify(token, jwks, { algorithms: ["ES256"], clockTolerance: "30s", // applies to nbf, exp and iat }); - 2.
修改配置之前,先打印时间线
比较 `nbf` 与验证方的当前时间。相差几秒说明是时钟偏差;相差几小时说明颁发者存在时区或单位错误;13 位数的 `nbf` 说明用了毫秒。
js import { decodeJwt } from "jose"; const { iat, nbf, exp } = decodeJwt(token); const now = Math.floor(Date.now() / 1000); console.log({ iat, nbf, exp, now, nbfInSeconds: nbf - now }); - 3.
修正漂移的时钟
在每台签发或验证令牌的主机上运行 NTP(chrony 或 systemd-timesyncd)。容差可以掩盖小的时钟偏差,但不应该靠它来掩盖大的偏差。
bash timedatectl status # "System clock synchronized: yes" chronyc tracking # offset from the NTP reference date -u +%s # compare with the token's nbf