OIDC 注销
一旦最终用户能够登录到您的应用程序,考虑他们将如何注销非常重要。
一般来说,有三个用例供您考虑:
-
我只想执行本地注销
-
我想注销我的应用程序和由我的应用程序启动的 OIDC 提供商
-
我想注销我的应用程序和由 OIDC 提供商发起的 OIDC 提供商
本地注销
要执行本地注销,不需要特殊的 OIDC 配置。
Spring Security 会自动建立本地注销端点,您可以通过logout()
DSL (英语).
OpenID Connect 1.0 客户端发起的注销
OpenID Connect 会话管理 1.0 允许使用客户端在提供程序处注销最终用户。 可用的策略之一是 RP 发起的注销。
如果 OpenID Provider 同时支持 Session Management 和 Discovery,则客户端可以获取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 中建立 Back-Channel Logout 终端节点,如下所示:
-
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 以便通过 BackChannel 正确注销每个会话。 |
反向通道注销架构
考虑一个ClientRegistration
其标识符为registrationId
.
Back-Channel 注销的总体流程如下:
-
在登录时, Spring Security 将 ID 令牌、CSRF 令牌和提供者会话 ID(如果有)与其应用程序中的会话 ID 相关联
ReactiveOidcSessionRegistry
实现。 -
然后,在注销时,您的 OIDC 提供商会对
/logout/connect/back-channel/registrationId
包括一个 Logout Token,该令牌指示sub
(最终用户)或sid
(Provider Session ID) 注销。 -
Spring Security 验证令牌的签名和声明。
-
如果令牌包含
sid
claim,则仅终止与该 provider 会话相关的 Client 会话。 -
否则,如果令牌包含
sub
claim,则该 Client 对该 End User 的所有会话都将终止。
请记住,Spring Security 的 OIDC 支持是多租户的。
这意味着它只会终止 Client 与aud claim 的 Token。 |
自定义会话注销端点
跟OidcBackChannelServerLogoutHandler
published,则会话注销端点为{baseUrl}/logout/connect/back-channel/{registrationId}
.
如果OidcBackChannelServerLogoutHandler
未连接,则 URL 为{baseUrl}/logout/connect/back-channel/{registrationId}
,不建议这样做,因为它需要传递 CSRF 令牌,这可能具有挑战性,具体取决于您的应用程序使用的存储库类型。
如果需要自定义端点,可以按如下方式提供 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 Provider Session Registry
默认情况下,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(...);
}
}