OIDC 登出
一旦最終使用者能夠登入您的應用程式,考慮他們將如何登出是很重要的。
一般來說,您需要考慮三個使用案例
-
我只想執行本地登出
-
我想登出我的應用程式和 OIDC Provider,由我的應用程式啟動
-
我想登出我的應用程式和 OIDC Provider,由 OIDC Provider 啟動
本地登出
若要執行本地登出,不需要特殊的 OIDC 組態。Spring Security 會自動啟動本地登出端點,您可以透過 logout()
DSL 進行組態。
OpenID Connect 1.0 Client 啟動的登出
OpenID Connect Session Management 1.0 允許透過 Client 登出 Provider 上的最終使用者。其中一種可用的策略是 RP 啟動的登出。
如果 OpenID Provider 同時支援 Session Management 和 Discovery,client 可以從 OpenID Provider 的 Discovery Metadata 取得 end_session_endpoint
URL
。您可以透過使用 issuer-uri
組態 ClientRegistration
來做到這一點,如下所示
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 啟動的登出的 OidcClientInitiatedServerLogoutSuccessHandler
,如下所示
-
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 登出
OpenID Connect Session Management 1.0 允許透過 Provider 對 Client 進行 API 呼叫,以登出 Client 上的最終使用者。這被稱為 OIDC Back-Channel 登出。
若要啟用此功能,您可以在 DSL 中啟動 Back-Channel 登出端點,如下所示
-
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()
}
就是這樣!
這將啟動端點 /logout/connect/back-channel/{registrationId}
,OIDC Provider 可以請求該端點以使最終使用者在您的應用程式中的指定 session 失效。
oidcLogout 需要也組態 oauth2Login 。 |
oidcLogout 需要 session Cookie 稱為 JSESSIONID ,以便透過 backchannel 正確登出每個 session。 |
Back-Channel 登出架構
考量一個 identifier 為 registrationId
的 ClientRegistration
。
Back-Channel 登出的整體流程如下
-
在登入時,Spring Security 會將 ID Token、CSRF Token 和 Provider Session ID (如果有的話) 與您的應用程式 session id 關聯在其
ReactiveOidcSessionRegistry
實作中。 -
然後在登出時,您的 OIDC Provider 會對
/logout/connect/back-channel/registrationId
進行 API 呼叫,其中包含一個 Logout Token,指示要登出的sub
(最終使用者) 或sid
(Provider Session ID)。 -
Spring Security 會驗證 token 的簽章和宣告。
-
如果 token 包含
sid
宣告,則只會終止與該 provider session 相關聯的 Client session。 -
否則,如果 token 包含
sub
宣告,則會終止該最終使用者的所有 Client session。
請記住,Spring Security 的 OIDC 支援是多租戶的。這表示它只會終止 Client 與 Logout Token 中的 aud 宣告相符的 session。 |
自訂 OIDC Provider Session Registry
預設情況下,Spring Security 會在記憶體中儲存 OIDC Provider session 和 Client session 之間的所有連結。
在許多情況下,例如叢集應用程式,最好將其儲存在不同的位置,例如資料庫。
您可以透過組態自訂 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(...);
}
}