KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
02 · 重定向与回跳 — keel 龙骨
类型:主题概念 · 应用课对应:02 章 · 授权码流与 PKCE
类型:主题概念 · 应用课对应:02 章 · 授权码流与 PKCE
这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。它不讲怎么配客户端。
一、它为什么会出现
应用需要 IdP 给的那段结论,但它不能自己去要:它一旦能自己去要,就意味着它得先拿到用户的密码。
于是出现了一个很别扭的安排:让用户眼前的浏览器替应用跑一趟。 麻烦也全在这里——浏览器会留下痕迹,而这一趟里带的东西必须经得起被别人看见。
两个现象:
① 点了"用 SSO 登录",地址栏跳到 IdP,输完密码,跳回来 —— 页面一片空白
② 浏览器地址栏里明明躺着 ?state=…&iss=…&code=…,应用却像是没收到
它面对的需求是:把"结论"从 IdP 带回应用,而这趟路全程不许出现密码,也不许出现能被直接使用的凭据。
二、它是什么
一句话:重定向是"应用把浏览器交给 IdP"的那一跳;回跳是"IdP 把浏览器连同结论交回应用"的那一跳。两跳都由浏览器亲自走。
它不是下面三样东西:
- 不是应用之间的一次 HTTP 调用。 应用没有偷偷去调 IdP;换掉的是你眼前那个浏览器的地址,你的网络面板里看不到应用发的这个请求。
- 不是"回跳地址是一个随便填的配置项"。
redirect_uri是安全边界:IdP 只肯把结论交回预先登记过的地址,别的地址一律拒绝。 - 不是"跳回来就没事了"。 回跳地址里带回来的东西要当场核(
state、iss、code),核不过就不能往下走。
三、它长什么样
第一跳,一次真实的授权请求(环境:Keycloak 26.8.1,dev 模式,realm = sso-lab):
http://localhost:8081/realms/sso-lab/protocol/openid-connect/auth
?client_id=sp-spa
&redirect_uri=http%3A%2F%2Flocalhost%3A8090%2Fcallback
&response_type=code
&scope=openid+profile+email
&state=AsoTwZFFJyDK-urhFMy_jw
&nonce=_MJjvwPYDooEWutUkyihuA
&code_challenge=8Vklj4QX3rXa9CurOob2v3rc6v8ei2wZzj6s7Uij7tY
&code_challenge_method=S256
第二跳,IdP 交回浏览器时给的那一行(HTTP 响应头原文):
status = 302
Location = http://localhost:8090/callback?state=AsoTwZFFJyDK-urhFMy_jw&session_state=VnXksyjhgJexcsLOoOE5fc8c&iss=http%3A%2F%2Flocalhost%3A8081%2Frealms%2Fsso-lab&code=44f80113-b402-a38e-bbc0-56310447ebf8.VnXksyjhgJexcsLOoOE5fc8c.74717848-0bf1-4f44-81f2-e31422eb12c8
回跳带回来的参数是四个:code、iss、session_state、state。其中两个是有防御职责的:
| 参数 | 防的是什么 | 比对的是"谁" |
|---|---|---|
state |
别人伪造一次回跳,让你的应用去换令牌 | 比对自己这次发起时发出去的那个值 |
iss |
Mix-Up:多个 IdP 之间被掉包 | 比对自己登记过的那个 IdP 地址 |
iss 是 RFC 9207 定义的"授权响应发行者标识",它和令牌里的 iss 是同一个值(01 页)。回跳地址里带 iss,是为了让应用在换令牌之前就能判断"这个响应是不是我信的那个 IdP 发的"。
如果那个回跳地址没登记过,IdP 不会把结论交回来:
status = 400
页面上的错误 = Invalid parameter: redirect_uri
四、它怎么工作
一次完整的来回是四步:
① 应用生成 state(以及 nonce),把浏览器重定向到 IdP 的授权端点
↓
② 浏览器在 IdP 的地址上完成登录 ← 密码只在这里出现
↓
③ IdP 把浏览器重定向回预先登记的 redirect_uri,带上 code / state / iss
↓
④ 应用当场核 state 与 iss;都对上,才拿 code 去换令牌
第 ③ 步是整件事的枢纽:这一趟经过浏览器的地址里,只有 code 是"凭据类"的东西,而它一次就用废(03 页)。真正能用的令牌在第 ④ 步背后换,不进地址栏。
第 ① 步发出去的 state 与第 ④ 步收回来的 state 必须是同一个值——这是应用唯一能验证"这次回跳是我要的那一次"的手段。
五、它会带出哪些概念
- 授权码与 PKCE:第 ③ 步交回来的
code不是令牌,只能用一次,还得配一份只有应用知道的code_verifier才能换出东西。→ 03 页 - 断言与令牌:第 ④ 步换回来的东西;回跳里的
iss与令牌里的iss是同一个值。→ 01 页 - 会话与单点登出:第 ② 步 IdP 顺手记下的那份会话,就是"第二个应用不用再输密码"的原因。→ 04 页
六、回到应用课
| 应用课里的那句话 | 现在你知道它为什么成立 |
|---|---|
| "回跳回来一片空白" | 空白常常是回跳回来过了,只是应用的处理里没把 code 换成令牌;问题不在"没回来",而在"回来了没人接" |
"state 与 iss 是两个容易被当成'装饰'的参数" |
它们各自证明一件事:state 证明这次回跳对应我发起的那一次,iss 证明响应来自我登记的那个 IdP |
"redirect_uri 是结论送回去的正确地址,送错地方等于把身份送给别人" |
正因为它是一个"送回去的地址",IdP 只肯把它交给预先登记过的那一个;没登记的地址一律 400 |
这一页改变了你的哪个判断:看到回跳地址里那一长串参数时,你不再只当它是系统自带的噪声,而是先去比对 state 是不是自己发出去的那个、iss 是不是自己登记的那个 IdP——这两项任何一项对不上,这次回跳就不能用。
→ 这一页的展开在应用课 02 章 · 授权码流与 PKCE 与 04 章 · 会话与登出。
自测题
state与iss各防的是哪一类问题?各自比对的是"谁"?- 一个没登记过的
redirect_uri会被怎么处理?从这一条看,它更像一个配置项,还是一条安全边界?