LabHub
学习 学习路径 课程

企业认证对接

手工复现 OIDC 授权码流程

在 LabHub 中继续学习

目标

面对实验用 OIDC 提供者,亲手完成授权码流程,解码并验证 ID token 的签名,再编写验证检查清单。

为什么重要

只通过库使用过 OIDC 的人,发生故障时往往不知道该看哪里。redirect_uri 不匹配、未验证 state、缺少 nonce、重复使用过期 code——只有亲眼看过完整流程,才能形成直觉。

尤其重要的是亲手确认:ID token 采用 base64,任何人都能解码。如果修改 payload 后仍不验证签名,伪造内容也会直接通过。亲自复现后,就会真正理解为什么必须固定 alg

步骤

  1. 启动实验用 IdP。 python3 /opt/lab/fixtures/auth/oidc/idp.py 9000(后台运行) 获取 discovery 文档并保存到 /root/oidc/discovery.json。 (http://127.0.0.1:9000/.well-known/openid-configuration) 必须包含 issuerauthorization_endpointtoken_endpointjwks_uri
  2. /root/oidc/auth-url.txt 中写入一行 authorization URL。必需参数:response_type=codeclient_id=labhub-webredirect_uri=http://127.0.0.1:9100/callbackscope=openid profile emailstate=<16자 이상>nonce=<16자 이상>
  3. 调用该 URL 时不要跟随重定向,将响应的 Location header 保存到 /root/oidc/callback.txt,再从中只提取 code 值保存到 /root/oidc/code.txt。Location 中的 state 必须与第 2 步发送的值一致。
  4. 在 token endpoint 用 code 换取 token,将完整响应 JSON 保存到 /root/oidc/token.json。同时发送 client_id=labhub-webclient_secret=labhub-secret。必须包含 access_tokenid_tokentoken_type,且 token_typeBearer
  5. 解码 id_tokenpayload,保存到 /root/oidc/claims.json。必须包含 issaudsubexpnonceaud 必须为 labhub-webnonce 必须与第 2 步发送的值一致。
  6. 创建 /root/oidc/verify.sh。它接收两个参数(ID토큰 공개키PEM),签名有效时退出码为 0,否则为非零。公钥位于 /opt/lab/fixtures/auth/oidc/idp-public.pem
  7. 使用 access_token 调用 userinfo endpoint,将结果保存到 /root/oidc/userinfo.jsonsub 值必须与第 5 步 claims.json 中的 sub 相同。
  8. 创建 /root/oidc/checklist.csv,第一行为 item,risk서명issaudexpnoncealg 六项都必须出现在 item 中;risk 中要用至少 10 个字符说明不验证该项可能导致的攻击或事故。

参考

启动 IdP 并获取 discovery 文档

启动实验用 IdP。 python3 /opt/lab/fixtures/auth/oidc/idp.py 9000(后台运行) 获取 discovery 文档并保存到 /root/oidc/discovery.json。 (http://127.0.0.1:9000/.well-known/openid-configuration) 必须包含 issuerauthorization_endpointtoken_endpointjwks_uri

OIDC 提供者会在标准路径提供配置文档。仅凭这一份文档即可获知全部 endpoint 地址。试着用 jq 提取所需值。

组装 authorization URL

/root/oidc/auth-url.txt 中写入一行 authorization URL。 必需参数:response_type=codeclient_id=labhub-webredirect_uri=http://127.0.0.1:9100/callbackscope=openid profile emailstate=<16자 이상>nonce=<16자 이상>

遗漏任何必需参数都会使 IdP 返回错误。state 与 nonce 的职责不同:一个防止 CSRF,另一个防止 token replay。

接收 authorization code

调用该 URL 时不要跟随重定向,将响应的 Location header 保存到 /root/oidc/callback.txt,再从中只提取 code 值 保存到 /root/oidc/code.txt。 Location 中的 state 必须与第 2 步发送的值一致。

即使没有浏览器,也可读取重定向响应的 Location header 来查看 code。必须让 curl 不跟随重定向。

交换 token

在 token endpoint 用 code 换取 token,将完整响应 JSON 保存到 /root/oidc/token.json。 同时发送 client_id=labhub-webclient_secret=labhub-secret。 必须包含 access_tokenid_tokentoken_type,且 token_typeBearer

token endpoint 使用 POST 和 form 格式。authorization code 只能使用一次,第二次会失败;失败后请重新执行第 2~3 步。

解码 ID token payload

解码 id_tokenpayload,保存到 /root/oidc/claims.json。 必须包含 issaudsubexpnonceaud 必须为 labhub-webnonce 必须与第 2 步发送的值一致。

JWT 由点号分隔的三部分组成,每部分都是 base64url。base64url 与标准 base64 有两个字符不同,而且可能没有 padding。

编写签名验证脚本

创建 /root/oidc/verify.sh。它接收两个参数(ID토큰 공개키PEM), 签名有效时退出码为 0,否则为非零。 公钥位于 /opt/lab/fixtures/auth/oidc/idp-public.pem

签名对象是完整的“header.payload”字符串。可使用 openssl dgst 和公钥验证,签名值需要先进行 base64url 解码。

调用 userinfo

使用 access_token 调用 userinfo endpoint, 将结果保存到 /root/oidc/userinfo.jsonsub 值必须与第 5 步 claims.json 中的 sub 相同。

access token 通过 Authorization header 以 Bearer 方式发送。本步骤的关键是确认返回的 sub 与 ID token 中的 sub 相同。

编写 ID token 验证检查清单

创建 /root/oidc/checklist.csv,第一行为 item,risk서명issaudexpnoncealg 六项都必须出现在 item 中; risk 中要用至少 10 个字符说明不验证该项可能导致的攻击或事故。

不要只写“为什么需要该项”,而要写“不验证会遭受什么攻击”。这样在后续 review 中更有说服力。