LabHub
学习 学习路径 课程

Keycloak 与企业认证

JWT 的解析与签名、声明校验

在 LabHub 中继续学习

目标

手动拆解 JWT、验证签名并检查 claim,从而理解库替你完成的工作究竟是什么。

为什么重要

JWT 验证通常只需一行库调用,因此很容易在不了解内部原理的情况下略过。但如果那一行的选项配置错误,就会悄无声息地产生漏洞。典型例子是没有启用 aud 检查时,同一认证服务器为其他 API 签发的令牌也能通过本 API。若不验证 alg,现实中还曾发生过利用 none 算法使无签名令牌通过的事故。本实验最后测量的数字也很重要:introspection 每次请求都需要一次网络往返,而本地验证只是 CPU 运算。如果十个微服务分别询问,认证服务器就会成为瓶颈。因此实际工作中通常将访问令牌寿命设为 5~15 分钟,并使用本地验证。这是一种把撤销生效延迟限制为令牌寿命的权衡。

步骤

  1. 使用 /root/kc/wait.sh 等待 http://127.0.0.1:8080/realms/master 返回 200,最长等待 180 秒。在 /root/kc/ready.txt 中写入 ready_seconds=<정수>
  2. 使用 /opt/fixtures/kc/realm-info.env 中的值签发令牌,只把访问令牌以单行保存到 /root/kc/token.txt。它必须是包含 2 个点的字符串。
  3. 使用 /root/kc/decode.py 将 header 保存到 /root/kc/header.json,payload 保存到 /root/kc/claims.json。两者都必须是有效 JSON。
  4. /root/kc/claimcheck.txt 中写入 iss=<값> aud=<값> sub=<값> exp_in=<남은초>exp_in 必须大于 0。
  5. http://127.0.0.1:8080/realms/labhub/protocol/openid-connect/certs 获取 JWKS,将与 header 中 kid 匹配的 key 保存到 /root/kc/jwk.jsonkty 必须是 RSA
  6. 使用 /root/kc/verify.py 验证签名,并在 /root/kc/verify.out 中写入 signature=valid alg=<값>
  7. 使用 payload 被篡改的令牌进行相同验证,并在 /root/kc/tamper.out 中写入 signature=invalid

参考

等待 Keycloak 准备就绪

使用 /root/kc/wait.sh 等待 http://127.0.0.1:8080/realms/master 返回 200,最长等待 180 秒。在 /root/kc/ready.txt 中写入 ready_seconds=<정수>

由于使用 JVM,启动较慢。请用循环轮询就绪状态,并留出充足的最长等待时间。

签发令牌

使用 /opt/fixtures/kc/realm-info.env 中的值签发令牌,只把访问令牌以单行保存到 /root/kc/token.txt。它必须是包含 2 个点的字符串。

预先导入的 realm 信息位于 /opt/fixtures/kc/realm-info.env。向令牌端点发送表单数据。

解码 header 与 payload

使用 /root/kc/decode.py 将 header 保存到 /root/kc/header.json,payload 保存到 /root/kc/claims.json。两者都必须是有效 JSON。

它们是由点分隔的三个片段中的前两个。Base64URL 可能没有 padding,因此需要补齐。

检查必要 claim

/root/kc/claimcheck.txt 中写入 iss=<값> aud=<값> sub=<값> exp_in=<남은초>exp_in 必须大于 0。

分别检查 issuer、audience、expiration 和 subject。遗漏任何一个都会留下漏洞。

从 JWKS 中寻找签名 key

http://127.0.0.1:8080/realms/labhub/protocol/openid-connect/certs 获取 JWKS,将与 header 中 kid 匹配的 key 保存到 /root/kc/jwk.jsonkty 必须是 RSA

通过 header 中的 key identifier 从列表中选择。轮换期间可能同时存在多个 key。

验证签名

使用 /root/kc/verify.py 验证签名,并在 /root/kc/verify.out 中写入 signature=valid alg=<값>

签名对象是用点连接 header 和 payload 得到的字符串,而不是解码后的 JSON。

确认篡改令牌会被拒绝

使用 payload 被篡改的令牌进行相同验证,并在 /root/kc/tamper.out 中写入 signature=invalid

即使只修改 payload 中一个字符,签名也应失效。