JWT 里面有什么?为什么解码不等于验证
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。
信任头部带来的问题
头部由发送令牌的人写入,因此验证方绝不能让头部决定验证规则。
alg: none。 没有签名的令牌可以声明"alg":"none"。曾经有一些库接受了这样的令牌,并跳过了检查。允许使用的算法应当在你自己的代码中设定,而不能从令牌里获取。- 算法混淆。 服务器期望使用 RS256,并且持有公钥。攻击者发送一个 HS256 令牌,把那个公钥当作 HMAC 密钥来签名。如果验证方接受令牌头部声明的任何算法,它就会接受这个令牌。每个密钥都应当只绑定一种算法。
- 弱密钥。 攻击者拿到了头部、载荷和签名,因此可以离线测试候选密钥。短密钥或常见词很快就会被这样的字典攻破。RFC 7518 规定,HS256 的密钥至少应为 256 位,并且是随机生成的。
把线上令牌粘贴到网站里
一个有效且未过期的令牌,对持有它的人来说就等同于有效凭证,直到它过期为止。请像对待密码一样对待它。在把令牌粘贴到任何网站之前,先弄清楚该网站是否会把它发送到服务器。完全在浏览器标签页中运行的工具没有这个必要。你可以打开浏览器的开发者工具,查看页面的网络请求,来核实这一点。
除非服务器维护了一份列表,否则令牌通常无法在过期前被撤销。如果一个正在使用的令牌泄露了,请让服务提供方撤销该会话或轮换其密钥。调试时,能用测试环境的令牌就尽量用测试环境的。JWT 解码器在你的浏览器标签页中读取三个部分,你粘贴的任何内容都不会被发送到别处。
Webhook:同样的思路,但使用共享密钥
大多数 Webhook 服务商会用你与他们共享的密钥,对请求体计算 HMAC,并把结果放在一个请求头里发送。你的接收端用同样的方法计算 HMAC,再把两者进行比较。常见的错误往往很细微:计算哈希前 JSON 被重新格式化,导致字节发生了变化;测试密钥被用于线上流量;或者使用普通的字符串相等比较来对比签名,而这种比较可能泄露时序信息。应当使用恒定时间比较。
Webhook 签名测试器会根据你粘贴的密钥和请求体计算签名,支持 GitHub、Stripe、Slack、Shopify 或任何 HMAC 方案,方便你查看两个值是否一致。
常见问题速答
JWT 是加密的吗?
不是。标准的 JWT 是经过签名的,任何人都能读取它的头部和载荷。只有签名能保护它不被篡改。
如果我修改了载荷会怎样?
签名就对不上了,验证服务器会拒绝这个令牌。读取声明并不会检查签名,所以解码器仍会显示你修改后的版本。
由 AI 辅助翻译自英文原文。
评论
还没有评论。快来抢沙发。


