OIDC Logout
当最终用户能够登录您的应用程序时,请务必考虑他们的注销方式。 通常情况下,需要考虑三种使用场景:
-
我只想执行本地注销
-
由我的应用程序发起的注销我的应用程序和 OIDC 提供商
-
由 OIDC 提供商发起的注销我的应用程序和 OIDC 提供商
Local Logout
为了执行本地注销,无需特殊的 OIDC 配置,Spring Security 会自动建立一个本地注销端点,您可以使用 xref:servlet/authentication/logout.adoc[configure through the logout()
DSL。
OpenID Connect 1.0 Client-Initiated Logout
OpenID Connect 会话管理 1.0 允许客户端退出提供程序中的最终用户。其中一种可用的策略是 RP-Initiated Logout 。
如果 OpenID 提供程序同时支持会话管理和 Discovery ,则客户端可以从 OpenID 提供程序的 Discovery Metadata 中获得 end_session_endpoint
URL
。配置 ClientRegistration
及其 issuer-uri
如下所示,便可以执行此操作:
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-secret: okta-client-secret
...
provider:
okta:
issuer-uri: https://dev-1234.oktapreview.com
此外,您应当如下所示,配置实现了 RP 发起注销的 OidcClientInitiatedLogoutSuccessHandler
:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(withDefaults())
.logout(logout -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler())
);
return http.build();
}
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
// Sets the location that the End-User's User Agent will be redirected to
// after the logout has been performed at the Provider
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return oidcLogoutSuccessHandler;
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Autowired
private lateinit var clientRegistrationRepository: ClientRegistrationRepository
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2Login { }
logout {
logoutSuccessHandler = oidcLogoutSuccessHandler()
}
}
return http.build()
}
private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler {
val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository)
// Sets the location that the End-User's User Agent will be redirected to
// after the logout has been performed at the Provider
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
return oidcLogoutSuccessHandler
}
}
|
OpenID Connect 1.0 Back-Channel Logout
OpenID Connect 会话管理 1.0 允许提供程序向客户端发起 API 调用,从而将最终用户退出客户端。这称为 OIDC Back-Channel Logout 。
为了启用它,您可以这样在 DSL 中建立后端注销端点:
-
Java
-
Kotlin
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2Login(withDefaults()) .oidcLogout((logout) -> logout .backChannel(Customizer.withDefaults()) ); return http.build(); }
@Bean open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeRequests { authorize(anyRequest, authenticated) } oauth2Login { } oidcLogout { backChannel { } } } return http.build() }
然后,您需要想办法监听 Spring Security 发布的事件,来移除旧的 OidcSessionInformation
条目,如下所示:
-
Java
-
Kotlin
@Bean public HttpSessionEventListener sessionEventListener() { return new HttpSessionEventListener(); }
@Bean open fun sessionEventListener(): HttpSessionEventListener { return HttpSessionEventListener() }
这将使如 HttpSession#invalidate
被调用,会话也会从内存中移除。
这就是全部!
这将建立端点 /logout/connect/back-channel/{registrationId}
,OIDC 提供商能请求该端点来使应用程序中最终用户给定会话失效。
|
|
Back-Channel Logout Architecture
考虑标识符为 registrationId
的 ClientRegistration
。
后端登出流程的总体流程如下:
-
在登录时,Spring Security 将 ID 令牌、CSRF 令牌和提供者会话 ID(如果存在)与您的应用程序的会话 id 相关联,在其中实现
OidcSessionStrategy
。 -
然后在注销时,您的 OIDC 提供商将调用 API
/logout/connect/back-channel/registrationId
,其中包括一个注销令牌,用于指示sub
(最终用户)或sid
(提供者会话 ID)进行注销。 -
Spring Security 验证令牌的签名和声明。
-
如果令牌包含 `sid`声明,则仅终止与该提供者会话相关联的 Client 的会话。
-
否则,如果令牌包含 `sub`声明,则终止该 Client 的所有与最终用户相关的会话。
请记住,Spring Security 的 OIDC 支持是多租户的。这意味着它只会终止其客户端与注销令牌中的 |
Customizing the OIDC Provider Session Strategy
默认情况下,Spring Security 在内存中存储 OIDC Provider 会话和客户端会话之间的所有链接。
存在多个情况,例如集群应用程序,存储在一个单独的位置(如数据库)中会很好。
你可以通过配置一个自定义 OidcSessionStrategy
来实现此目的:
-
Java
-
Kotlin
@Component public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy { private final OidcProviderSessionRepository sessions; // ... @Override public void saveSessionInformation(OidcSessionInformation info) { this.sessions.save(info); } @Override public OidcSessionInformation(String clientSessionId) { return this.sessions.removeByClientSessionId(clientSessionId); } @Override public Iterable<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) { return token.getSessionId() != null ? this.sessions.removeBySessionIdAndIssuerAndAudience(...) : this.sessions.removeBySubjectAndIssuerAndAudience(...); } }
@Component class MySpringDataOidcSessionStrategy: OidcSessionStrategy { val sessions: OidcProviderSessionRepository // ... @Override fun saveSessionInformation(info: OidcSessionInformation) { this.sessions.save(info) } @Override fun removeSessionInformation(clientSessionId: String): OidcSessionInformation { return this.sessions.removeByClientSessionId(clientSessionId); } @Override fun removeSessionInformation(token: OidcLogoutToken): Iterable<OidcSessionInformation> { return token.getSessionId() != null ? this.sessions.removeBySessionIdAndIssuerAndAudience(...) : this.sessions.removeBySubjectAndIssuerAndAudience(...); } }