fixmint
ZH

JWT 里面有什么?为什么解码不等于验证

作者: · · 阅读约 4 分钟

JSON Web Token(JWT)是由点号连接的三段文本:头部、载荷和签名。前两部分只是编码,并没有加密,所以持有令牌的人都能读到其中的内容。签名则让服务器能够确认令牌确实由可信的一方签发,并且没有被人篡改。解码一个令牌,只能告诉你它写了什么;只有验证它,才能判断是否可以相信它。

JWT 解码器令牌过期了,还是仍然有效?免费使用

三个部分

令牌的结构是 header.payload.signature。jwt.io 上那个广为人知的示例令牌,第一部分是这样的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

前两部分各自都是 JSON 经过 base64url 编码的结果。base64url 是一种可以安全用于 URL 的文本格式。第一部分解码后是:

{"alg":"HS256","typ":"JWT"}

同一个示例的载荷解码后是:

{"sub":"1234567890","name":"John Doe","iat":1516239022}

载荷存放的是声明(claims),也就是关于用户或会话的陈述。签名由前两部分加上一个密钥计算得出,所以只要改动一个字符,签名就对不上了。base64url 不需要密钥就能还原,因此绝不要把密码、私钥或个人数据放进载荷。

注册声明

JWT 的标准 RFC 7519 为几个简短的声明名称做了保留:

  • iss(签发者):谁创建了这个令牌。
  • sub(主题):这个令牌是关于谁的,通常是用户 ID。
  • aud(受众):这个令牌是给哪个服务用的。
  • exp(过期时间):超过这个时刻后,令牌失效。
  • nbf(生效时间):令牌最早生效的时刻。
  • iat(签发时间):令牌的创建时间。

时间以 UTC 1970 年 1 月 1 日以来的秒数表示,称为 Unix 时间。示例中没有 exp,按照标准,它永远不会过期。真实的令牌应当设置这个字段。role、email 之类的其他名称由应用自己定义。

解码不等于验证

解码就是按点号把令牌拆开,还原 base64url,再读取 JSON。它展示的是令牌声称的内容,而伪造的令牌同样能被这样解码,因为任何人都可以用任意签名写入任意声明。

验证则是用正确的密钥重新计算签名,并检查两者是否一致。它还要检查 exp 和 nbf 是否把当前时间包含在有效范围内,以及 iss 和 aud 是否是服务所期望的值。如果应用在没有验证的情况下直接读取 role 声明,那它就是在信任任何写出这个令牌的人。这是一个很常见的错误。

HS256、RS256 和 ES256

alg 的值说明了所用的算法。常见的有三种:

  • HS256 是基于 SHA-256 的 HMAC。同一个共享密钥既用来签名,也用来验证,因此这个密钥必须只留在签发令牌的服务器上。
  • RS256 是基于 SHA-256 的 RSA 签名。签发方用私钥签名,任何持有公钥的人都能验证。其他服务可以检查令牌,却无法自行创建令牌。
  • ES256 是采用 P-256 曲线和 SHA-256 的 ECDSA 签名。它与 RS256 的工作方式相似,但密钥和签名都更小。

如果同一个服务既签发又检查自己的令牌,用 HS256 即可。如果其他服务需要检查它们并非自己签发的令牌,则用 RS256 或 ES256。

信任头部带来的问题

头部由发送令牌的人写入,因此验证方绝不能让头部决定验证规则。

  1. alg: none。 没有签名的令牌可以声明 "alg":"none"。曾经有一些库接受了这样的令牌,并跳过了检查。允许使用的算法应当在你自己的代码中设定,而不能从令牌里获取。
  2. 算法混淆。 服务器期望使用 RS256,并且持有公钥。攻击者发送一个 HS256 令牌,把那个公钥当作 HMAC 密钥来签名。如果验证方接受令牌头部声明的任何算法,它就会接受这个令牌。每个密钥都应当只绑定一种算法。
  3. 弱密钥。 攻击者拿到了头部、载荷和签名,因此可以离线测试候选密钥。短密钥或常见词很快就会被这样的字典攻破。RFC 7518 规定,HS256 的密钥至少应为 256 位,并且是随机生成的。

把线上令牌粘贴到网站里

一个有效且未过期的令牌,对持有它的人来说就等同于有效凭证,直到它过期为止。请像对待密码一样对待它。在把令牌粘贴到任何网站之前,先弄清楚该网站是否会把它发送到服务器。完全在浏览器标签页中运行的工具没有这个必要。你可以打开浏览器的开发者工具,查看页面的网络请求,来核实这一点。

除非服务器维护了一份列表,否则令牌通常无法在过期前被撤销。如果一个正在使用的令牌泄露了,请让服务提供方撤销该会话或轮换其密钥。调试时,能用测试环境的令牌就尽量用测试环境的。JWT 解码器在你的浏览器标签页中读取三个部分,你粘贴的任何内容都不会被发送到别处。

Webhook:同样的思路,但使用共享密钥

大多数 Webhook 服务商会用你与他们共享的密钥,对请求体计算 HMAC,并把结果放在一个请求头里发送。你的接收端用同样的方法计算 HMAC,再把两者进行比较。常见的错误往往很细微:计算哈希前 JSON 被重新格式化,导致字节发生了变化;测试密钥被用于线上流量;或者使用普通的字符串相等比较来对比签名,而这种比较可能泄露时序信息。应当使用恒定时间比较。

Webhook 签名测试器会根据你粘贴的密钥和请求体计算签名,支持 GitHub、Stripe、Slack、Shopify 或任何 HMAC 方案,方便你查看两个值是否一致。

常见问题速答

JWT 是加密的吗?

不是。标准的 JWT 是经过签名的,任何人都能读取它的头部和载荷。只有签名能保护它不被篡改。

如果我修改了载荷会怎样?

签名就对不上了,验证服务器会拒绝这个令牌。读取声明并不会检查签名,所以解码器仍会显示你修改后的版本。

由 AI 辅助翻译自英文原文。

评论

    还没有评论。快来抢沙发。

    你的评分(可选)

    你的邮箱不会被显示或保存。

    Agentic AI in the Browser 的封面Agentic AI in the Browser

    隐私优先、基于浏览器的AI工作流应用案例,体现的是fixmint所依托的“在设备上直接完成”这一理念。

    $4.99在 Gumroad 获取英文阅读