前通道登出

按照标准https://openid.net/specs/openid-connect-rpinitiated-1_0.html ,登录平台会通过发现端点(Discovery Endpoint)来公布自己的结束会话端点,比如下图展示了基于 Duende IdentityServer 部署的服务 id6.azurewebsites.net 的发现端点:
end_session_endpoint.png
当用户在当前应用退出时,当前应用会将用户重定向到登录平台的结束会话端点,同时带上以下 3 个参数:

  • id_token_hint:用户的身份令牌。登录平台会校验该令牌是否有效,如果有效,则会清除该用户的会话信息;如果该参数缺失,则登录平台会展示一个页面,让用户确认是否退出登录;这是为了防止 CSRF 攻击,即攻击者通过一个链接,让用户在不知情的情况下退出登录。
  • post_logout_redirect_uri:用户退出登录后,登录平台会将用户重定向到该 URL。该参数是可选的,如果缺失,则登录平台会将用户重定向到登录平台的主页。如果该参数存在,则必须是登录平台已经注册过的 URL。
  • state: 如果客户端在重定向到登录平台的结束会话端点时,传递了有效的 post_logout_redirect_uri 参数,则可以选择同时传递这个 state 参数。当用户退出登录后,登录平台会将用户重定向到 post_logout_redirect_uri 参数指定的 URL,并在 URL 中带上 state 参数。这可以帮助客户端在多次跳转中不丢失状态信息。

一般请求格式是这样的:

GET /connect/endsession?id_token_hint=...&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A7017%2Findex.html

后通道登出

类似的,登录平台也会通过发现端点来公布自己是否支持后通道来结束会话,如下图所示:
backchannel_logout.png
Keycloak 还提供了 UI 来对该选项进行配置:
keycloak-clients-logout-settings-1.png
keycloak-clients-logout-settings-2.png
要使用后通道模式退出用户,需要在当前应用中向登录平台发送一个 HTTP POST 请求 https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRequest ,同时带上一个 logout_token 参数,该参数是一个 JWT 格式的身份令牌。该请求看上去是这样的:

csharp POST /backchannel_logout HTTP/1.1 Host: rp.example.org Content-Type: application/x-www-form-urlencoded

logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ...

如上所示,该 POST 请求的请求体使用了 application/x-www-form-urlencoded 编码的格式,而不是 application/json 格式。
支持后通道模式退出的身份认证平台或者说登录平台在收到这样的请求后,需要校验 logout_token 参数是否有效,如果有效,则清除该用户的会话信息。如果 logout_token 参数无效,则登录平台会返回一个 HTTP 400 错误码。
检验规则如下:

  1. 如果 logout_token 被加密了,则登录平台需要使用客户端在注册时指定的身份令牌加密算法和密钥来解密该令牌。如果在客户端注册阶段商定了身份令牌的加密,但是在退出登录时传入的 logout_token 没有被加密,则登录平台应该拒绝该请求,返回 HTTP 400 错误码。
  2. 验证 logout_token 的签名是否有效,并且满足接下来的每一条规则。
  3. 验证 alg(算法)头部参数。和身份令牌一样,算法的选择在发现端点中由 id_token_signing_alg_values_supported 和 id_token_signed_response_alg 参数指定,如果没有指定,则默认为 RS256。要注意的是,在 logout_token 中绝不允许使用 alg = none 的算法。
  4. 验证 iss(签发者)头部参数。该参数必须是登录平台的 URL,或者是登录平台的 URL 的子域名。
  5. 验证 aud(受众)头部参数。该参数必须是当前应用的客户端 ID,或者是当前应用的客户端 ID 的子域名。
  6. 验证 iat(签发时间)头部参数。该参数必须是一个有效的 UNIX 时间戳,且必须早于当前时间。
  7. 验证 logout_token 中必须包含 sub(主题)声明或者sid(会话 ID)声明,或者同时包含这两个声明。
  8. 验证 logout_token 中必须包含 events(事件)声明,且该声明必须是一个 JSON 对象,并且必须包含一个成员,其值是 http://schemas.openid.net/event/backchannel-logout
  9. 确认 logout_token 中一定没有 nonce(随机数)声明。
  10. 可以验证最近没有收到过同样的 jti 值的 logout_token (该规则不是强制的)。
  11. 可以验证 logout_token 中的 iss 声明和当前会话或者最近的会话中的身份令牌中的 iss 声明一致(该规则不是强制的)。
  12. 可以验证 logout_token 中的 sub 声明和当前会话或者最近的会话中的身份令牌中的 sub 声明一致(该规则不是强制的)。
  13. 可以验证 logout_token 中的 sid 声明和当前会话或者最近的会话中的身份令牌中的 sid 声明一致(该规则不是强制的)。

以上验证步骤,只要有一个失败,就应该拒绝该请求,返回 HTTP 400 错误码。如果全部通过,就执行退出登录操作。

Duende IdentityServer 尽管没有 UI 可以配置,但仍然可以使用代码来设置是否支持,并且是默认开启的。

遗憾的 authing.cn 目前还不支持该选项,我在 authing.cn 中创建了一个应用,从其 well known 端点里没有找到支持的字段:
https://brickverse.authing.cn/oidc/.well-known/openid-configuration 。去论坛里提问了,得到的回复是还没有实现。[https://forum.authing.cn/t/topic/1703](https://forum.authing.cn/t/topic/1703 ) 。
image.png

小结

本文介绍了如何实现前通道和后通道两种方式的登出操作。前通道是指在用户退出当前应用时,将用户重定向到登录平台的结束会话端点,让登录平台清除用户的会话信息。后通道是指在用户退出当前应用时,向登录平台发送一个 HTTP POST 请求,让登录平台清除用户的会话信息。