此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3! |
OIDC 注销
一旦最终用户能够登录到您的应用程序,就必须考虑他们将如何注销。
一般来说,有三个用例可供您考虑:
-
我只想执行本地注销
-
我想注销我的应用程序和由我的应用程序启动的 OIDC 提供程序
-
我想注销我的应用程序和由 OIDC 提供程序启动的 OIDC 提供程序
本地注销
要执行本地注销,不需要特殊的 OIDC 配置。
Spring Security 会自动建立一个本地注销端点,您可以通过logout()
DSL的.
OpenID Connect 1.0 客户端启动的注销
OpenID Connect 会话管理 1.0 允许使用客户端注销提供商处的最终用户。 可用的策略之一是 RP 发起的注销。
如果 OpenID 提供程序同时支持会话管理和发现,则客户端可以获取end_session_endpoint
URL
从 OpenID 提供程序的发现元数据中。
您可以通过配置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
此外,您应该配置OidcClientInitiatedServerLogoutSuccessHandler
,它实现了 RP 发起的注销,如下所示:
-
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 反向通道注销
OpenID Connect 会话管理 1.0 允许通过让提供程序对客户端进行 API 调用来注销客户端上的最终用户。 这称为 OIDC 反向通道注销。
要启用此功能,您可以在 DSL 中建立反向通道注销端点,如下所示:
-
Java
-
Kotlin
@Bean
OidcBackChannelServerLogoutHandler oidcLogoutHandler() {
return new OidcBackChannelServerLogoutHandler();
}
@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
fun oidcLogoutHandler(): OidcBackChannelLogoutHandler {
return OidcBackChannelLogoutHandler()
}
@Bean
open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
oidcLogout {
backChannel { }
}
}
return http.build()
}
就是这样!
这将建立端点/logout/connect/back-channel/{registrationId}
OIDC 提供程序可以请求使应用程序中最终用户的给定会话失效。
oidcLogout 要求oauth2Login 也要配置。 |
oidcLogout 要求调用会话 cookieJSESSIONID 以便通过反向通道正确注销每个会话。 |
反向通道注销架构
考虑一个ClientRegistration
其标识符为registrationId
.
后台通道注销的总体流程如下所示:
-
登录时,Spring Security 将 ID Tokens、CSRF Tokens和提供程序会话 ID(如果有)与应用程序的会话 ID 相关联。
ReactiveOidcSessionRegistry
实现。 -
然后,在注销时,您的 OIDC 提供程序会对
/logout/connect/back-channel/registrationId
包括一个注销Tokens,该Tokens指示sub
(最终用户)或sid
(提供商会话 ID)注销。 -
Spring Security 验证Tokens的签名和声明。
-
如果Tokens包含
sid
claim,则只有与该提供程序会话相关的客户端会话才会终止。 -
否则,如果Tokens包含
sub
claim,则该客户端对该最终用户的所有会话都将终止。
请记住,Spring Security 的 OIDC 支持是多租户的。
这意味着它只会终止客户端与aud 声明。 |
自定义会话注销端点
跟OidcBackChannelServerLogoutHandler
已发布,会话注销端点为{baseUrl}/logout/connect/back-channel/{registrationId}
.
如果OidcBackChannelServerLogoutHandler
未连接,则 URL 是{baseUrl}/logout/connect/back-channel/{registrationId}
,不建议这样做,因为它需要传递 CSRF Tokens,这可能具有挑战性,具体取决于您的应用程序使用的存储库类型。
如果需要自定义终结点,可以按如下方式提供 URL:
-
Java
-
Kotlin
http // ... .oidcLogout((oidc) -> oidc .backChannel((backChannel) -> backChannel .logoutUri("http://localhost:9000/logout/connect/back-channel/+{registrationId}+") ) );
http { oidcLogout { backChannel { logoutUri = "http://localhost:9000/logout/connect/back-channel/+{registrationId}+" } } }
自定义会话注销 Cookie 名称
默认情况下,会话注销端点使用JSESSIONID
cookie 将会话与相应的OidcSessionInformation
.
但是,Spring Session 中的默认 cookie 名称是SESSION
.
您可以在 DSL 中配置 Spring Session 的 cookie 名称,如下所示:
-
Java
-
Kotlin
@Bean OidcBackChannelServerLogoutHandler oidcLogoutHandler(ReactiveOidcSessionRegistry sessionRegistry) { OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(sessionRegistry); logoutHandler.setSessionCookieName("SESSION"); return logoutHandler; }
@Bean open fun oidcLogoutHandler(val sessionRegistry: ReactiveOidcSessionRegistry): OidcBackChannelServerLogoutHandler { val logoutHandler = OidcBackChannelServerLogoutHandler(sessionRegistry) logoutHandler.setSessionCookieName("SESSION") return logoutHandler }
自定义 OIDC 提供程序会话注册表
默认情况下,Spring Security将OIDC Provider会话和Client会话之间的所有链接存储在内存中。
在许多情况下,例如群集应用程序,最好将其存储在单独的位置,例如数据库。
您可以通过配置自定义ReactiveOidcSessionRegistry
这样:
-
Java
-
Kotlin
@Component
public final class MySpringDataOidcSessionRegistry implements ReactiveOidcSessionRegistry {
private final OidcProviderSessionRepository sessions;
// ...
@Override
public Mono<void> saveSessionInformation(OidcSessionInformation info) {
return this.sessions.save(info);
}
@Override
public Mono<OidcSessionInformation> removeSessionInformation(String clientSessionId) {
return this.sessions.removeByClientSessionId(clientSessionId);
}
@Override
public Flux<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
return token.getSessionId() != null ?
this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
this.sessions.removeBySubjectAndIssuerAndAudience(...);
}
}
@Component
class MySpringDataOidcSessionRegistry: ReactiveOidcSessionRegistry {
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(...);
}
}