3Q工具箱
首页 / 教程中心 / 开发辅助

JWT 排查手册:三段结构、验签算法、过期时间与那些「没过期却 401」

开发辅助发布于 2026-09-12

JWT 的排查难点在于它「看起来像密文」。一串不可读的字符让人本能地以为里面的内容受保护,于是有人往 payload 里塞手机号,有人以为改不了就不用校验签名。这两个误解各自对应一类真实事故。

这篇按令牌从解析到验签到时间判定的顺序讲一遍。所有解析和验签都在浏览器里跑完,令牌与密钥不上传,但线上正在使用的长效令牌仍建议脱敏后再贴——它等价于一个账号。

一、三段结构与 Base64URL,payload 不是密文

一个 JWS 形态的 JWT 由三段组成,用点号分隔:

header.payload.signature

前两段是 JSON 文本做 Base64URL 编码后的结果,第三段是对「前两段拼起来的字符串」计算出的签名。注意编码用的是 Base64URL 而不是标准 Base64,区别有三处:+ 换成 -/ 换成 _,尾部的 = 填充被去掉。这个差异是很多手写解码脚本报错的原因,也是为什么把 JWT 的中间一段直接丢给通用解码器可能失败。

编码不是加密。任何拿到令牌的人都能直接读出 payload 里的全部内容,不需要密钥。所以下面这些绝对不能放进 payload:身份证号、手机号、邮箱、真实姓名、内部用户表主键之外的业务敏感字段、以及任何权限决策的「原因」细节。放用户 ID 和角色标识是常规做法,放「余额」「工资档级」就是泄露。

JWT解析与验签把令牌贴进去,Header、Payload 会分别格式化展示,标准声明单独列表并把时间戳转成可读时间,过期或尚未生效会直接标色。想验证「编码确实不需要密钥」这件事,可以用Base64编码手工走一遍:把中间那段的 - 替换回 +_ 替换回 /,按 4 的倍数补上 =,再点解码,你会看到完整的 JSON。

签名段保护的是完整性,不是保密性。它保证「内容没被改过」,不保证「内容不被看到」。真的需要保密就得用 JWE,或者干脆改用不透明的随机串加服务端会话存储。

二、验签算法族,HMAC 与 RSA/ECDSA 的差异

Header 里的 alg 指明签名算法,实际用到的分两类,运维含义完全不同。

  • HS256 / HS384 / HS512(HMAC):对称。签发和验证用同一个密钥。优点是快、实现简单;缺点是所有需要验签的服务都得持有这把密钥,而持有它就等于能签发任意令牌。微服务架构里把 HMAC 密钥分发给十几个服务,等于把签发权分给了十几个服务。
  • RS256 / RS384 / RS512(RSA PKCS#1 v1.5)与 PS256 系列(RSA-PSS):非对称。私钥签发、公钥验证。公钥可以随便发,网关和各个业务方只拿公钥,不具备签发能力。这是多方验签场景的正确选择。
  • ES256 / ES384 / ES512(ECDSA):同样非对称,但签名更短、密钥更小,性能优于同等安全强度的 RSA,是新系统的推荐项。

在工具里验签时,两类的操作也不同:HMAC 系列需要填密钥,并且要选对密钥编码,UTF-8Base64Hex 三种解读会得到完全不同的结果,对接方文档没写清时逐个试是最快的办法;RS、PS、ES 系列需要粘贴以 -----BEGIN PUBLIC KEY----- 开头的 SPKI 格式 PEM 公钥,验签由浏览器内置的 Web Crypto 完成,因此页面需要运行在 HTTPS 或 localhost 下。绿色表示签名有效,红色表示签名无效或密钥不匹配。

一个高频误判:验签失败不一定是密钥错。如果令牌是从日志或聊天记录里复制的,先确认有没有被换行截断、有没有多余空格、有没有把结尾的字符吃掉。签名段少一个字符就必然失败。

三、exp、nbf、iat 都是 Unix 秒,不是毫秒

RFC 7519 规定 expnbfiat 的类型是 NumericDate,即自 1970-01-01 UTC 起的秒数,允许小数但不是毫秒。这一条踩坑率极高,因为 JavaScript 的 Date.now() 返回毫秒。

  • 签发方写成毫秒exp 变成一个 13 位数字,落在公元 5 万年附近,于是令牌永不过期。这类 bug 平时毫无症状,等到有人需要吊销令牌时才发现根本没有过期机制。
  • 验证方按毫秒解读秒级值:10 位数字被当成毫秒,换算出 1970 年的某天,于是所有令牌都「已过期」,表现为登录后立刻 401。

判断方法极简单:看位数。10 位是秒,13 位是毫秒。要把这些数字换算成可读时间、或反过来算出某个时刻对应的秒级时间戳,用时间转换器最直接,它会自动识别输入格式并同时给出 ISO 8601、RFC 系列、UTC 等十六种标准表示,便于和服务端日志里的时间格式逐一对齐。

三个声明的语义也别混:iat 是签发时刻,nbf 是「在此之前不生效」,exp 是「在此之后失效」。有效期不是靠 iat 加一个约定时长推算出来的,服务端必须显式检查 exp;只看 iat 而不看 exp 的实现等于没有有效期。另外 exp 的判定按 RFC 是「当前时间必须小于 exp」,等于的那一瞬间已经算过期,边界测试时注意这一秒。

四、明明没过期却返回 401,先查时钟

这是最消耗时间的一类问题,因为代码和令牌看起来都对。按下面的顺序查,通常三步之内能定位。

  • 服务器时钟漂移。令牌有效期是 5 分钟,而签发服务和验证服务的系统时间差了 8 分钟,那么刚签发的令牌在验证方看来已经过期。虚拟机休眠恢复后、NTP 未配置的容器里,几分钟的漂移很常见。解法是所有节点强制启用 NTP 同步,并在验签时允许一个小的时钟容差(业界惯例 30 到 60 秒),不要为了掩盖漂移把容差设成 10 分钟。
  • 时区被算进了时间戳。Unix 时间戳本身没有时区,它就是一个绝对时刻。如果签发代码先把本地时间格式化成字符串再解析成时间戳,就可能引入 8 小时偏移,表现为令牌提前 8 小时过期或延后 8 小时生效。凡是出现「差了整数个小时」的偏移,都往时区处理上查。
  • nbf 设成了签发时刻且没有容差。签发瞬间写入 nbf,验证方时钟稍慢一点,就会判定「令牌尚未生效」,同样返回 401 但原因和过期相反。工具会把尚未生效的令牌单独标出来,这一眼就能区分。
  • 有效期太短叠加网络耗时。有效期设成 10 秒的令牌,遇到重试和排队就会在到达服务端时刚好过期。访问令牌的常规区间是 5 到 30 分钟,靠刷新令牌续期,而不是把访问令牌的有效期设成一天。
  • 401 根本不是过期引起的。签名密钥轮换后旧令牌全部失效、audiss 校验不通过、令牌被主动加入黑名单,都会返回 401。先看服务端日志里的具体拒绝原因,再回来看令牌。

五、alg 为 none 与算法混淆攻击

JWT 规范里有一个 alg: none,表示「无签名」,签名段为空。它的存在是为了支持令牌已被其他机制保护的场景,但在真实系统里,任何一个接受 none 的验签实现都等于把权限拱手让人:攻击者把 Header 改成 none、把 payload 里的角色改成管理员、删掉签名段,就通过了。工具遇到 algnone 的令牌会直接给出安全警告,看到这个提示就该去查服务端的验签配置。

更隐蔽的是算法混淆攻击。系统本来用 RS256,公钥是公开的。攻击者把 Header 里的 alg 改成 HS256,然后用那份公开的公钥字符串当作 HMAC 密钥去签名。如果服务端的验签代码是「读取令牌里的 alg,按它选择算法,用配置里的那份密钥材料验证」,那么它会拿公钥当 HMAC 密钥验签,结果通过。攻击者由此获得任意令牌的签发能力,而他用到的全部材料都是公开的。

防法只有一条,而且必须写死在代码里:

  • **服务端固定期望算法,绝不采信令牌 Header 里的 alg**。验签接口传入的算法应来自配置常量,而不是解析结果。
  • 主流库提供了显式指定算法的参数,务必使用。不要调用「自动检测算法」的便捷方法。
  • 对称与非对称的密钥材料分开存放,从物理上避免公钥被当成 HMAC 密钥使用的可能。
  • kid 字段只用于在自己的密钥集合里查找,不能当路径或 URL 使用。把 kid 直接拼进文件路径或数据库查询,会引入路径穿越与注入。
  • **同时校验 issaud**。签名有效只说明「某个我们信任的签发方签的」,不说明「这个令牌是给我这个服务用的」。缺少 aud 校验时,A 服务的令牌可以拿去访问 B 服务。

常见问题

JWT 能不能主动吊销? 标准 JWT 是无状态的,签出去就在有效期内一直有效,这是它的设计取舍。要支持吊销就得引入状态:维护一份黑名单(按 jti 记录到过期为止)、或者缩短访问令牌有效期并把吊销做在刷新令牌上。后者是更常见的工程做法。

refresh token 该不该也用 JWT? 通常不需要。刷新令牌本来就要在服务端存状态以支持吊销和一次性使用,用不透明随机串更合适,也避免了把长效凭据的内容暴露给客户端。

为什么我用在线解码器解 payload 会报错? Base64URL 与标准 Base64 的字符集不同且没有填充。手工处理时把 - 换成 +_ 换成 /,再把长度补到 4 的倍数(缺几个补几个 =)就能正常解码。

验签通过了是不是就可以完全信任 payload? 签名有效只保证内容未被篡改、来自持有密钥的签发方。是否过期、是否针对本服务(aud)、是否来自预期签发方(iss)、权限是否仍然有效(用户可能已被停用),这些都要另外校验。把签名有效直接当成授权通过,是另一类常见漏洞。

文中用到的工具

同类教程