KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
04 · 会话与单点登出 — keel 龙骨
类型:主题概念 · 应用课对应:04 章 · 会话与登出
类型:主题概念 · 应用课对应:04 章 · 会话与登出
这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。它不讲怎么接具体某一种登出协议。
一、它为什么会出现
SSO 的卖点是"登录一次,处处可用"。按这个逻辑,"退出一次,处处失效"听起来应该自动成立。
它不自动成立。 因为"已经登录过"这件事被记在了不止一个地方:IdP 侧记一份,每个应用侧又各记一份,而它们之间不会互相通知。
三个现象:
① 在工单系统点了"退出",看到了登录页
② 切到报表系统,还是登着 —— 因为报表只查自己的会话,没问 IdP
③ 若还有第三个系统(比如一个没接单点登出的旧系统),它可能一直登着
它面对的需求是:让"登出"也变成一次广播,而不是每个应用各清各的。
二、它是什么
一句话:IdP 侧的会话是"这台浏览器在本 IdP 登录过"的记录;应用侧的会话是"这个应用认得这个人"的记录。两份记录,两个地方。
单点登出就是一次把这两处一起清掉的动作——难的不是清,是"通知得到"。
它不是下面三样东西:
- 不是同一份"登录状态"。 IdP 侧和应用侧各记一份,两者可以不同步——"退出了还进得去"正是这份不同步。
- 不是"删掉浏览器里的 cookie 就完事"。 删的是浏览器那一份;只要 IdP 侧那份还在,下一次访问可能被无声地重新登进去(这正是 SSO 的另一面)。
- 不是"一次调用能通知到所有应用"。 通知要走一条通道(经浏览器的前端通道,或服务端直连的后端通道);没接这条通道的应用,收不到。
三、它长什么样
登录成功与登出之后,浏览器侧留下的 cookie 是两份不同的清单(环境:Keycloak 26.8.1,realm = sso-lab):
登录成功后的 cookie:AUTH_SESSION_ID, KC_AUTH_SESSION_HASH, KEYCLOAK_IDENTITY, KEYCLOAK_SESSION
登出之后 的 cookie:AUTH_SESSION_ID, KC_AUTH_SESSION_HASH, KC_RESTART
对比一下:消失的是 KEYCLOAK_IDENTITY 与 KEYCLOAK_SESSION,而 AUTH_SESSION_ID、KC_AUTH_SESSION_HASH、KC_RESTART 都还在。KEYCLOAK_IDENTITY 才是"已登录"这件事的载体——它没了,就代表 IdP 侧那份会话被作废了。
这一行里有个容易误读的地方:IdP 给这些会话 cookie 加的是
Secure; SameSite=None。在http://localhost下,浏览器把 localhost 当成可信来源,会照常回传它们;而一个普通的 HTTP 客户端不会回传Securecookie,所以用脚本复现这段报文时要显式带上Cookie头。这是脚本与浏览器的差异,不代表浏览器也登不上。
单点登录成立的样子:同一个浏览器会话去访问第二个应用,第一步 GET 授权端点就直接 302 带 code,全程没有出现登录页:
GET auth -> 302 | Location: http://localhost:8090/callback?state=…&session_state=…&iss=…&code=…
★ 是否复用已有会话 = True
★ 整个流程里没有出现登录页,也没有再提交一次密码
sp-spa 的 ID Token: sub = 8b0e33a7-5e5b-4051-a20e-417fa5b1cf5d | aud = sp-spa
sp-portal 的 ID Token: sub = 8b0e33a7-5e5b-4051-a20e-417fa5b1cf5d | aud = sp-portal
两个应用拿到的 sub 是同一个值,aud 各自不同——这就是"同一个人、两封信"的判据(01 页)。
登出走的是 IdP 的一个端点,它登记在发现文档里:
end_session_endpoint = http://localhost:8081/realms/sso-lab/protocol/openid-connect/logout
登出把浏览器送过去,IdP 清掉自己那份,再把它送回应用:
GET logout -> 302 | Location: http://localhost:8090/callback
四、它怎么工作
① 首次登录 IdP 侧写下自己那份会话(就是上面那批 cookie 里的 KEYCLOAK_IDENTITY)
② 第二个应用来问 IdP 发现"这台浏览器已经有会话" → 直接给结论,不弹登录页
③ 发起登出 应用把浏览器送到 IdP 的 end_session_endpoint
④ 清 + 通知 IdP 清掉自己那份,再通过通道通知各个应用清它们各自那份
第 ④ 步是"难"的全部所在:登录是"一个应用来问一次",登出是"一次通知到所有应用"。应用数目越多、越杂(有的还留着旧系统没接入),这一格的覆盖面就越不全。会话通常比 access token 活得久(access token 300 秒,人不会 300 秒登一次),所以中间撑住访问的是会话,而这份会话的存在,正是登出必须清两处的原因。
五、它会带出哪些概念
- IdP 与 SP:两份会话分别属于谁、由谁清。→ 00 页
- 断言与令牌:令牌的寿命(access 300 秒)和会话的寿命不是一回事;
sid把令牌和 IdP 侧那次会话串起来。→ 01 页 - 重定向与回跳:登出同样靠浏览器跑一趟,回到应用登记的地址。→ 02 页
六、回到应用课
| 应用课里的那句话 | 现在你知道它为什么成立 |
|---|---|
| "登出一个系统,另一个还登着" | 另一个应用只查自己的会话;要让它也失效,得靠 IdP 主动通知它 |
| "单点登出为什么难:会话在两侧,IdP 只能清自己那一侧" | 清掉 IdP 侧那份会话,并不连带作废已经发出去的应用侧会话——那是另一处状态 |
| "「用户界面上显示已退出」与「服务端已作废」需要分别验证" | 「已登录」的载体是 IdP 侧那个 cookie;它消失才是服务端真的作废,界面上看到登录页只是其中一种表现 |
这一页改变了你的哪个判断:遇到"退出后还进得去",你不再只看当前这个应用的登录态,而是先分流——是 IdP 侧那份会话没清掉,还是这个应用根本没接登出通道——这两件事的修法完全不同。
→ 这一页的展开在应用课 04 章 · 会话与登出 与 06 章。
自测题
- 登录成功后浏览器侧多了四个 cookie,登出后只剩三个——消失的那两个说明了什么?
- "单点登出"为什么比"单点登录"难?难在哪一步、因为什么?