OIDC Logout
当最终用户能够登录您的应用程序时,请务必考虑他们的注销方式。
Once an end user is able to login to your application, it’s important to consider how they will log out.
通常情况下,需要考虑三种使用场景:
Generally speaking, there are three use cases for you to consider:
-
I want to perform only a local logout
-
I want to log out both my application and the OIDC Provider, initiated by my application
-
I want to log out both my application and the OIDC Provider, initiated by the OIDC Provider
Local Logout
无需特殊 OIDC 配置即可执行本地注销。Spring Security 会自动启用一个本地注销端点,可以 configure through the logout()
DSL使用这个端点。
To perform a local logout, no special OIDC configuration is needed.
Spring Security automatically stands up a local logout endpoint, which you can configure through the logout()
DSL.
OpenID Connect 1.0 Client-Initiated Logout
OpenID Connect 会话管理 1.0 允许客户端退出提供程序中的最终用户。其中一种可用的策略是 RP-Initiated Logout 。
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Provider by using the Client. One of the strategies available is RP-Initiated Logout.
如果 OpenID 提供程序同时支持会话管理和 Discovery ,则客户端可以从 OpenID 提供程序的 Discovery Metadata 中获得 end_session_endpoint
URL
。配置 ClientRegistration
及其 issuer-uri
如下所示,便可以执行此操作:
If the OpenID Provider supports both Session Management and Discovery, the client can obtain the end_session_endpoint
URL
from the OpenID Provider’s Discovery Metadata.
You can do so by configuring the ClientRegistration
with the issuer-uri
, as follows:
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
此外,您应该配置 OidcClientInitiatedServerLogoutSuccessHandler
,它实现 RP 发起登出,如下所示:
Also, you should configure OidcClientInitiatedServerLogoutSuccessHandler
, which implements RP-Initiated Logout, as follows:
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Autowired
private ReactiveClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2Login(withDefaults())
.logout((logout) -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler())
);
return http.build();
}
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(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
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Autowired
private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
@Bean
open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
logout {
logoutSuccessHandler = oidcLogoutSuccessHandler()
}
}
return http.build()
}
private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(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 。
OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Client by having the Provider make an API call to the Client. This is referred to as OIDC Back-Channel Logout.
为了启用它,您可以这样在 DSL 中建立后端注销端点:
To enable this, you can stand up the Back-Channel Logout endpoint in the DSL like so:
-
Java
-
Kotlin
@Bean public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception { http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .oauth2Login(withDefaults()) .oidcLogout((logout) -> logout .backChannel(Customizer.withDefaults()) ); return http.build(); }
@Bean open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain { http { authorizeExchange { authorize(anyExchange, authenticated) } oauth2Login { } oidcLogout { backChannel { } } } return http.build() }
这就是全部!
And that’s it!
这将建立端点 /logout/connect/back-channel/{registrationId}
,OIDC 提供商能请求该端点来使应用程序中最终用户给定会话失效。
This will stand up the endpoint /logout/connect/back-channel/{registrationId}
which the OIDC Provider can request to invalidate a given session of an end user in your application.
|
|
|
|
Back-Channel Logout Architecture
考虑标识符为 registrationId
的 ClientRegistration
。
Consider a ClientRegistration
whose identifier is registrationId
.
后端登出流程的总体流程如下:
The overall flow for a Back-Channel logout is like this:
-
At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application’s session id in its
ReactiveOidcSessionStrategy
implementation. -
Then at logout time, your OIDC Provider makes an API call to
/logout/connect/back-channel/registrationId
including a Logout Token that indicates either thesub
(the End User) or thesid
(the Provider Session ID) to logout. -
Spring Security validates the token’s signature and claims.
-
If the token contains a
sid
claim, then only the Client’s session that correlates to that provider session is terminated. -
Otherwise, if the token contains a
sub
claim, then all that Client’s sessions for that End User are terminated.
请记住,Spring Security 的 OIDC 支持是多租户的。这意味着它只会终止其客户端与注销令牌中的 |
Remember that Spring Security’s OIDC support is multi-tenant.
This means that it will only terminate sessions whose Client matches the |
Customizing the OIDC Provider Session Strategy
默认情况下,Spring Security 在内存中存储 OIDC Provider 会话和客户端会话之间的所有链接。
By default, Spring Security stores in-memory all links between the OIDC Provider session and the Client session.
存在多个情况,例如集群应用程序,存储在一个单独的位置(如数据库)中会很好。
There are a number of circumstances, like a clustered application, where it would be nice to store this instead in a separate location, like a database.
您可以通过配置一个自定义 ReactiveOidcSessionStrategy
来实现此目的,如下所示:
You can achieve this by configuring a custom ReactiveOidcSessionStrategy
, like so:
-
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: ReactiveOidcSessionStrategy { val sessions: OidcProviderSessionRepository // ... @Override fun saveSessionInformation(info: OidcSessionInformation): Mono<Void> { return this.sessions.save(info) } @Override fun removeSessionInformation(clientSessionId: String): Mono<OidcSessionInformation> { return this.sessions.removeByClientSessionId(clientSessionId); } @Override fun removeSessionInformation(token: OidcLogoutToken): Flux<OidcSessionInformation> { return token.getSessionId() != null ? this.sessions.removeBySessionIdAndIssuerAndAudience(...) : this.sessions.removeBySubjectAndIssuerAndAudience(...); } }