KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
课程设计说明 · SSO 应用课 — keel 龙骨
SSO 工程课:协议、令牌与会话 的参考信息:课程设计说明 · SSO 应用课
为什么主线是「一次登录的来回」而不是「一堆协议名词」
认证这一层的知识,天然长成两张网:一张是名词网(OIDC / OAuth / SAML / 断言 / realm / claim),
一张是时序网(谁在什么时候把什么交给谁、什么只能用一次)。
按名词开课的结果,读者会背下定义却接不进系统——这正是本课要解决的读者卡点
(知道名词,接进去之后「重定向回来一片空白」)。所以本课把主线定成一次登录的来回:
每章都在同一条链路上加一层东西,加完就断一次、看它从哪里断。
01 选型:这条链路该走哪条协议 (站在链路外面看)
02 走通:GET 授权端点 → 登录 → 回跳 → 换 token
03 拆开:换回来的三个 token 各管什么,每一项校验防什么
04 换个视角:为什么第二个应用不用再登录,登出要清哪两处
05 抬视角:一个应用接多家 IdP 时,链路哪里会断
06 破坏:把前面所有结论当作已知,主动制造五类故障
07 替换:逐条把教学替身换成真实依赖
判断力的落点因此在 05、06、07 三章:选型、排障、上线。
02–04 是它们的证据基础,不是终点。
目标能力(读者学完能做什么)
| 能力 | 判断依据 |
|---|---|
| 选型 | 拿到一个 SSO 需求,能说出该走 OIDC 还是 SAML,并说出这个判断依赖哪几个事实(谁主导、客户端类型、要不要前端直接拿令牌、有没有既有企业 IdP) |
| 读报文 | 给出一次登录的请求 URL 与回跳 URL,能逐个参数说出它是谁生成的、用来防什么 |
| 拆令牌 | 给出一个 ID Token,能说出六项校验(签名、iss、aud、exp、nonce、at_hash)各自漏了会出什么事 |
| 定边界 | 能说出「单点登录成立」的判据是什么、它在什么条件下不成立(换浏览器、清 cookie、会话过期) |
| 做取舍 | 能说出 access token 该不该发给前端、角色信息该不该塞进令牌,以及两条路各自的代价 |
| 排障 | 登录失败时按「症状 → 去哪看 → 根因」的顺序定位,而不是逐个配置项试 |
| 上线 | 能列出教学单实例与真实部署之间必须替换的点,并说出每个点不换会出什么事 |
评估证据(每章靠什么证明学会)
本章的「证据」都要求可观察,不要求读者同意课程的判断:
| 章 | 可观察的验收物 |
|---|---|
| 01 | 对三个真实场景各给一条协议选择 + 一句「为什么不是另一条」;说清本课与《权限与多租户》的分工 |
| 02 | 跑出一次完整登录:第一步 GET 是 200 登录页、POST 后是 302 带四个参数、换 token 是 200;再造一次 PKCE 失败,观察失败之后正确 verifier 也换不回来 |
| 03 | 对同一个 ID Token 说出每一项 claim 的作用;把 access token 与 ID Token 的 aud 对上(一个是 client_id、一个是 account) |
| 04 | 用「第二个应用第一步 GET 的状态码」判定单点登录成立;登出前后对比 cookie,指出哪一个的消失代表「已登录」被作废 |
| 05 | 给出一个应用的「多 IdP 路由表」(按什么区分、claim 怎么映射、账号怎么关联),并说出 sub 为什么不能直接当账号 |
| 06 | 复现五类故障中的至少三类,每一条写出「症状 → 去哪看 → 根因」 |
| 07 | 把「教学替身 → 真实替换点」表填完整,并对每一项写出一句「不换会出什么事」 |
课程内的自测题只考察因果(为什么、漏了会怎样),不考察名词记忆。
教学替身与生产替换点
| 教学替身(本课/配套项目) | 真实依赖 | 不换会出什么事 |
|---|---|---|
MiniOP 进程内实现(course/project/src/minioidc/op.py) |
真实 IdP(Keycloak / 企业既有 IdP / 托管身份服务) | 单实例没有高可用,IdP 一停所有应用都登不进;也没有审计、账号生命周期、反自动化 |
| HS256 对称签名(标准库无 RSA 时的选择) | RS256 / ES256 非对称签名 + 公开 JWKS | 校验密钥即签发密钥:任何持有它的组件都能伪造身份 |
固定 kid 与内存密钥环 |
轮换中的密钥集(新旧并存,随 JWKS 公布) | 换密钥即全体掉线;或者旧密钥永久有效,等于轮换没发生 |
内存 codes / sessions 字典 |
共享缓存/数据库 + TTL 与并发控制 | 多副本部署时 code 在 A 副本签发、B 副本认不出;会话在重启后消失 |
固定时钟(Clock) |
服务器时间同步(如 NTP) | 时钟偏移会让 exp / iat 判断出现整段整段的误判 |
Secure; SameSite=None 写在 http://localhost 上 |
HTTPS + 正确的 cookie 属性 | 脚本与浏览器行为不一致:表现为「登录页能开、一提交就说会话不存在」 |
应用侧 Browser 类(管 cookie) |
真实浏览器 + 前后端分离场景下的存储选择 | 令牌放进 localStorage 与放进 HttpOnly cookie 的暴露面完全不同 |
内存里的 refresh_tokens 映射 |
刷新令牌的轮换 + reuse detection(可选开启) | 默认只轮换、旧令牌仍可用:令牌被窃取后不会被发现 |
| 单 realm 单 issuer | 多 realm / 多 IdP / 代理后的 issuer 一致性 | iss 字面量不一致时,现象与「签名不对」一模一样 |
与已有课程的分工
| 已有课程 | 本课只负责 |
|---|---|
| 《权限与多租户》 | 那边是授权(你能做什么)+ 数据隔离;本课是认证(你是谁)+ 身份怎么在系统间传递。05 章在「跨系统身份 vs 租户内身份」这一处与它对齐,其余不重复 |
| 《SSO 基础课》 | 那边负责「起一个 IdP、把一个应用接进去」的操作面;本课假定这些已经做过,直接讲报文、约束与故障 |
| 《SSO 概念课》 | 那边是每个名词「从哪来、是什么」的概念页;本课在每个名词第一次出现时只给一句够做判断的定义,并把概念页作为下钻入口 |
防止课程变成玩具的标准
每条结论都要能被一次故意破坏验证,做不到的结论不写进正文:
- 「PKCE 挡住了 code 被截获」——用错误 verifier 换,必须失败;且失败那一次也消耗 code;
- 「code 是一次性的」——同一个 code 第二次换,必须返回与「不存在」相同的错误;
- 「单点登录靠 cookie」——把 cookie 抹掉,第二个应用必须重新要密码;
- 「登出后要重新登录」——登出后先前的会话必须真的在 IdP 侧被删除,而不只是清了 cookie;
- 「旧 refresh token 不作废」——轮换之后用旧令牌再换一次,必须成功(这是默认行为,不是 bug)。
这些断言都在 course/project/tests/ 里有对应测试,失败路径断言的是具体 error 串。