KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

04 · 会话与单点登出 — keel 龙骨

类型:主题概念 · 应用课对应:04 章 · 会话与登出

类型:主题概念 · 应用课对应:04 章 · 会话与登出

这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。它不讲怎么接具体某一种登出协议。

一、它为什么会出现

SSO 的卖点是"登录一次,处处可用"。按这个逻辑,"退出一次,处处失效"听起来应该自动成立。

它不自动成立。 因为"已经登录过"这件事被记在了不止一个地方:IdP 侧记一份,每个应用侧又各记一份,而它们之间不会互相通知。

三个现象:

① 在工单系统点了"退出",看到了登录页
② 切到报表系统,还是登着 —— 因为报表只查自己的会话,没问 IdP
③ 若还有第三个系统(比如一个没接单点登出的旧系统),它可能一直登着

它面对的需求是:让"登出"也变成一次广播,而不是每个应用各清各的。

二、它是什么

一句话:IdP 侧的会话是"这台浏览器在本 IdP 登录过"的记录;应用侧的会话是"这个应用认得这个人"的记录。两份记录,两个地方。

单点登出就是一次把这两处一起清掉的动作——难的不是清,是"通知得到"。

它不是下面三样东西:

三、它长什么样

登录成功与登出之后,浏览器侧留下的 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 客户端不会回传 Secure cookie,所以用脚本复现这段报文时要显式带上 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 主动通知它
"单点登出为什么难:会话在两侧,IdP 只能清自己那一侧" 清掉 IdP 侧那份会话,并不连带作废已经发出去的应用侧会话——那是另一处状态
"「用户界面上显示已退出」与「服务端已作废」需要分别验证" 「已登录」的载体是 IdP 侧那个 cookie;它消失才是服务端真的作废,界面上看到登录页只是其中一种表现

这一页改变了你的哪个判断:遇到"退出后还进得去",你不再只看当前这个应用的登录态,而是先分流——是 IdP 侧那份会话没清掉,还是这个应用根本没接登出通道——这两件事的修法完全不同。

→ 这一页的展开在应用课 04 章 · 会话与登出 与 06 章。

自测题

  1. 登录成功后浏览器侧多了四个 cookie,登出后只剩三个——消失的那两个说明了什么?
  2. "单点登出"为什么比"单点登录"难?难在哪一步、因为什么?

进入 keel 阅读