KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
SSO 应用课:从「跳回来一片空白」到能自己排出故障顺序 — keel 龙骨
SSO 工程课:协议、令牌与会话 的参考信息:SSO 应用课:从「跳回来一片空白」到能自己排出故障顺序
7 章 · 每章都用同一套可运行的报文与一套可改的最小实现
接入 SSO 最难的一刻,不是配置写完的时候,而是它不工作时的排查。
回调地址上多了一个 ?code=...,页面一片空白;令牌换不回来,日志里只有一行 invalid_grant;
本地登出干净,另一个系统还登着。这些现象有一个共同点:每一步都是“明明按文档写了”。
看文档看不出问题,因为文档讲的是每一步该做什么,而故障出在步骤之间——
谁记住了什么、什么只能用一次、什么时候必须清掉。
这门课从报文出发:先把一次登录的每一个来回摊开,再把「哪一步之后什么不能再用」
变成可以亲手注入的代码,最后收在生产环境的替换点上。
三个现场
现场一:回调地址上带着 code,应用却什么都没拿到。
开发者把「换 token」这一步写在浏览器里,或者把 code_verifier 存在了 Session Storage。
换到一半失败,重试一次,服务器说 Code not valid——同一个 code 已经用不上了。code 是一次性的,而且失败的那一次也算用过。这一条不知道,排障就会往完全错误的方向找。
现场二:登录明明成功了,第二个应用还是弹登录页。
单点登录成立的条件不是「登录过」,而是浏览器侧那个 cookie 还在。
把「已登录」记在了应用自己的 session 里,IdP 就会在第二步笑眯眯地再要一次密码——
因为它不认识你。搞清楚「已登录」记在哪一侧,是单点登录能不能成立的分界线。
现场三:更换 IdP 地址之后,所有应用一起报 issuer 不匹配。
issuer 是一个字面量,不是「大致相同的地址」。http 与 https、有没有端口、
末尾有没有斜杠,差一个字符就是另一个 issuer。生产环境前面一旦加了代理,
应用收到的 iss 就可能不是它配置里那个——这类故障从现象上看和「签名不对」一模一样。
这门课回答什么
| 你可能正在纠结的问题 | 在哪一章 |
|---|---|
| OAuth 2.0 / OIDC / SAML 到底各管哪一层?我这个场景该选哪个? | 01 |
第一步 GET 为什么给的是登录页而不是 code?回跳那四个参数都是什么? |
02 |
PKCE 的 code_verifier 是给谁用的?为什么填错一次就再也换不回来? |
02 |
ID Token 和 access token 都叫 token,aud 为什么一个是客户端一个是 account? |
03 |
nonce / at_hash / sid / auth_time 各自在防什么? |
03 |
| 单点登录到底靠什么成立?登出为什么两个系统要一起清? | 04 |
一个应用要接多家 IdP,怎么区分?sub 能当账号用吗? |
05 |
| 登录失败但看不出哪一步断的,从哪查起? | 06 |
| 教学里单实例够用,真实部署要在哪些地方换掉? | 07 |
这门课不回答什么
- 不重复「授权」那一半。 认证回答「你是谁」,授权回答「你能做什么」。
权限模型、数据范围、多租户隔离在《权限与多租户》;
它的开篇就把「认证」划出去交给了身份课。本课只在 05 章的接缝处与它对齐一次。 - 不教怎么把 IdP 跑起来。 起服务、建 realm、点第一个登录,在《SSO 基础课》。
- 不做名词词典。 每个词从哪来、严格定义是什么,在《SSO 概念课》各有一页;
本课只在第一次用到时给一句够做判断的定义。 - 不讲企业级 IdP 的运维细节(集群拓扑、数据库选型、高可用、容量规划)——本课只到「哪些默认值必须换」为止。
章节地图
01 全景与选型 ────────────────────────────── 先站到高处:三条协议各在哪一层
│
▼
02 授权码流与 PKCE ────────── 03 令牌与校验 把一次登录逐步走通,再拆开三个 token
│ │
▼ ▼
04 会话与登出 ──────────────────── 单点登录靠什么成立、登出为什么要清两处
│
▼
05 联邦与多租户 ────────────── 一个应用接多个 IdP 时的区分与接缝
│
▼
06 故障注入与排障 ──────────── 五类故障的「症状 → 去哪看 → 根因」
│
▼
07 生产边界 ────────────────── 逐条把教学替身换成真实依赖
七章的分工是**「先走通、再拆开、再破坏、最后换掉」**:02 走通一次完整登录,
03 拆开它拿回来的三样东西,04 回答「为什么第二次不用再登录」,05 把视角从一个应用抬到多个系统,
06 把前面所有结论当作已知去制造故障,07 收在生产替换点上。
配套项目
course/project/ 是一个只用标准库的最小 OIDC 实现:一个最小 OP(授权端点 / 令牌端点 / JWKS / 会话 / 登出)
加一个最小 RP,加一个会管 cookie 的「浏览器」。它不替代 Keycloak,只做一件事:
把「协议报文里到底交换了什么」变成可读、可改、可注入故障的代码。
cd courses/identity/sso-engineering/course/project
python examples/01_authorization_code_flow.py # 一次登录的每一步
python examples/02_tampered_verifier.py # PKCE 失败,且失败也消耗 code
python -m pytest -q tests # 34 passed
六个示例的输出逐字可复现(ID 来自顺序工厂、时间来自可显式推进的时钟),
每一条失败路径都断言具体的 error 串。项目自身的说明见 project/README.md。
学完以后你能做什么
- 拿到一个 SSO 需求,能判断该走 OIDC 还是 SAML,并说清这个判断依赖哪几个事实;
- 把一次授权码流从头到尾说清:谁发起、谁记住什么、哪一步之后什么不能再用;
- 对着一份 ID Token 说出每一项校验各自防的是什么,知道哪一项漏了会出什么事;
- 解释「为什么第二个应用不用再输密码」,并知道这个结论在哪一步会失效;
- 一个应用接多家 IdP 时,说出怎么区分、
sub能不能当账号、离职回收要走哪几条路; - 登录失败时,按「症状 → 去哪看 → 根因」的顺序定位,而不是逐个配置项乱试;
- 把教学用单实例 IdP 与真实部署的差异逐条列出来:会话存储、令牌寿命、密钥轮换、代理下的
issuer。
关于证据口径
- 正文里的报文片段来自真实运行的输出,环境是 Keycloak 26.8.1(dev 模式),
Docker 跑在本机、宿主8081映射容器8080,realm 名sso-lab。 - 读形状,别背数字。
code/state/nonce/ 令牌长度 / 各类 id 都随环境变;
正文引用它们是为了让你看到参数的位置与关系,不是让你抄。 course/project/里的输出用 Python 3.13.14 跑出,ID 与时间都是确定性的,可以逐字复现。- 项目用 HS256 对称签名代替真实部署的 RS256/ES256——原因与代价写在
project/README.md,07 章把它当作一个必须替换的点来讨论。 - 协议细节以官方规范和文档为准(见
course/research-and-course-basis.md的出处清单),
课程里的判断与建议是课程自己的口径,两者分开标注。
课程导读(起点自检、章节地图表、主案例、前置要求)见 course/README.md。