密码学 24:JWT、JWS 与 JWE——可读令牌、签名令牌和加密令牌
订单 API 收到一个形如 xxxxx.yyyyy.zzzzz 的访问令牌。开发者看到第三段是签名,就把第二段用户信息当成“被加密过的秘密”;又有人看到验签通过,就跳过了这个令牌到底签给哪个服务、什么时候过期的检查。攻击者完全可以复制一个仍在有效期内的 bearer token,带着它走一条自己的 TLS 连接。问题并不是 JWT 这种格式“安全还是不安全”,而是把不同操作解决的问题说成了一回事。
看一个令牌时先问:它做了哪种操作
JWT 是在各方之间传递声明(claims)的格式及处理约定。常见 iss 标明发行者,aud 标明预期使用者,exp 给出过期时刻。JWT 可以承载为 JWS(签名或 MAC 保护),也可以承载为 JWE(加密),还可能有嵌套组合;不是看到三个点分隔段就能断言所有 JWT 都加密了。
JWS compact 有三段:受保护头、payload、签名。前两段是 base64url 编码;编码不提供保密性,拿到令牌的一方即使没有公钥,也能读出 payload 的 JSON 声明。持有者可能仍因未通过签名验证而不能可信地采用这些声明;可读与可信是两件不同的事。用 Ed25519 例子说明输入输出:
1 | |
公钥从哪里来不能靠攻击者令牌里的一段任意“key id”决定;预先配置发行者与受信公钥,才可把验证通过解释为该发行者签了这些字节。alg 也不能由外部输入随意换成弱算法或无签名;验证器按预期用途固定允许算法与密钥类型。RFC 8725 特别强调这类发行者、算法、跨类型令牌混用风险。
JWE compact 则有五段:受保护头、加密后的密钥段、IV、密文、认证标签。本文只展示 alg=dir:双方事先安全分发同一个对称密钥,因此第二段为空,不必再包装新的内容密钥;enc=A256GCM 表示内容由 AES-256-GCM 真正加密并附带标签。受保护头虽通常可见,但也进入 AEAD 的 AAD:篡改头字段不重算标签会在解密验证时失败。能解密的是持有对称密钥的参与方;它和“任何服务都能拿一个公开发行者公钥验签”的 JWS 是不同的信任与保密边界。
1 | |
只有认证标签验证成功,解密后数据才能交给后续处理。但 JWE 加密成功并不自动证明哪个独立发行方发送了令牌:持有同一直接加密密钥的一端也能制造新密文。解开 JWE 以后,仍要判断 iss、aud、时效和业务权限;如果需要区分独立的签发者及保密对象,应按用例选择适当的签名/加密组合并检查内外层验证顺序,而不是凭一层密文猜测身份。
盗用 bearer token 的攻击不破坏签名
bearer 的意思是“持有即可出示”,而不是“只原始签发方能够重复出示”。令牌被浏览器扩展、日志、错误配置的第三方或其他路径取走时,攻击者不需要篡改任何一个 bit,就能在有效期内把原样令牌转交给目标 API。服务端签名验证依旧正确,iss、aud 和 exp 也可能全部通过;如果业务没有额外的撤销、一次性消费或持有者绑定规则,“验签成功”并不能发现使用者已更换。
令牌即使写着“订单创建权限”,真正修改订单仍须资源服务的权限判定与状态变更事务。在钱包经 HTTPS RPC 提交测试交易的案例中,API 的 JWT 可能授权“谁能使用 RPC”,交易私钥对交易字节的签名另由链规则验证。两类认证都通过,仍不能推出余额充足、执行成功、区块确认或现实订单已交付。
分层实验:真正签名和真正加密
examples/cryptography/24_tokens.mjs 用 Node 22 的 node:crypto 真正生成临时 Ed25519 密钥对与 AES-GCM 对称密钥:JWS 明文 payload 可读,改声明而不重签会验签失败,签名有效但 aud 错或已过期也拒绝。再次出示同一个正确签名 token,当前验证函数仍会接受——这是没有消费状态的刻意失败边界。JWE 可以解密;换解密 key、篡改密文或更动受保护头,认证会失败。脚本不输出实际 token 或私钥,只输出正反例断言;测试时钟固定为 2026-10-06 00:00 UTC,不是已上线授权服务的时钟。
先按仓库内 examples/cryptography/README.md 完成环境准备;以下命令从仓库根目录执行。
1 | |
2026-10-06 UTC,两条命令退出码 0,3 项测试通过。这里的 compact 编码器只实现固定 EdDSA-JWS 和 dir/A256GCM-JWE 子集,不是可部署的 JOSE 解析器:没有处理重复 JSON 成员、密钥发现、未知扩展、签名中嵌套 JWE、多租户密钥路由等。实验没有 token 失窃的真实服务路径、撤销传播或一次性消费测试,不能把看见的布尔值改写成完整身份平台验收。
两道带答案的练习
画图题: 在“发行方、资源服务、持有者、窃取 bearer 的攻击者”四个框内,标出 JWS Ed25519 私钥、公钥和 JWT 明文分别能落在谁那里。攻击者拿到原样令牌但没有发行方私钥,在 exp 前、目标 aud 正确且服务端没有额外防盗用状态时,哪些检查会通过?
可核对答案: 发行方持私钥,资源服务由可信来源取得发行方公钥,持有者与窃取者都能解码 JWS payload;攻击者不能为新内容造合法签名,但原样令牌仍能通过签名、发行方、受众和时效检查。若要阻止后续滥用,还需另设泄漏后的撤销、持有者绑定或与业务相符的消费/权限策略。
变更题: 修改 24_tokens.mjs 的 JWS aud 为 other.test 并正确重签;另将 openJwe() 解密得到的 JSON 里的 exp 设为早于测试时钟并重新生成有效 GCM 密文。哪一步仍会成功,哪一步必须由后续策略明确拒绝?
可核对答案: 重签后 JWS 的密码学签名仍可正确验证,但 acceptJws 的受众检查拒绝;JWE 在知道对称密钥的前提下仍可成功认证解密过期 JSON,因为 openJwe 只返回明文、不检查 claims。必须另由使用 JWT 的业务策略检查 exp,不能将 GCM tag 当作有效期证明。若直接改密文字节而不重算 tag,则连解密认证都失败。
资料与导航
- RFC 7519、RFC 7515、RFC 7516:JWT、JWS、JWE 的不同对象与序列化。
- RFC 8037、RFC 8725:EdDSA JOSE 用法与 JWT 使用准则。
- 04 HMAC 为什么需要秘密 · 23 API 请求认证为什么还需规范化。





