OIDC 注销
一旦最终用户能够登录您的应用程序,重要的是要考虑他们将如何注销。
一般来说,您需要考虑三种用例
-
我只想执行本地注销
-
我想注销我的应用程序和 OIDC 提供者,由我的应用程序发起
-
我想注销我的应用程序和 OIDC 提供者,由 OIDC 提供者发起
本地注销
要执行本地注销,不需要特殊的 OIDC 配置。Spring Security 会自动建立一个本地注销端点,您可以通过 logout()
DSL 配置它。
OpenID Connect 1.0 客户端发起注销
OpenID Connect 会话管理 1.0 允许通过使用客户端在提供者处注销最终用户。可用的策略之一是RP 发起注销。
如果 OpenID 提供者同时支持会话管理和发现,客户端可以从 OpenID 提供者的发现元数据中获取 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
此外,您应该配置 OidcClientInitiatedLogoutSuccessHandler
,它实现了 RP 发起注销,如下所示
-
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 后端通道注销
OpenID Connect 会话管理 1.0 允许通过让提供者向客户端进行 API 调用,在客户端注销最终用户。这被称为OIDC 后端通道注销。
要启用此功能,您可以在 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 提供者可以请求它来使您应用程序中最终用户的给定会话失效。
oidcLogout 要求也配置 oauth2Login 。
|
oidcLogout 要求会话 cookie 称为 JSESSIONID ,以便通过回传通道正确注销每个会话。
|
回传通道注销架构
考虑一个标识符为 registrationId
的 ClientRegistration
。
回传通道注销的整体流程如下
-
在登录时,Spring Security 将 ID 令牌、CSRF 令牌和提供者会话 ID(如果有)与其应用程序的会话 ID 相关联,该 ID 在其
OidcSessionStrategy
实现中。 -
然后在注销时,您的 OIDC 提供者会向
/logout/connect/back-channel/registrationId
发出 API 调用,其中包含一个注销令牌,该令牌指示要注销的sub
(最终用户)或sid
(提供者会话 ID)。 -
Spring Security 验证令牌的签名和声明。
-
如果令牌包含
sid
声明,则仅终止与该提供者会话相关的客户端会话。 -
否则,如果令牌包含
sub
声明,则终止该客户端对该最终用户的所有会话。
请记住,Spring Security 的 OIDC 支持是多租户的。这意味着它只会终止其客户端与注销令牌中的 aud 声明匹配的会话。
|
自定义 OIDC 提供者会话策略
默认情况下,Spring Security 在内存中存储 OIDC 提供者会话和客户端会话之间的所有链接。
在某些情况下,例如集群应用程序,最好将此存储在其他位置,例如数据库。
您可以通过配置自定义 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(...); } }