OIDC 登出
一旦最終使用者能夠登入您的應用程式,考量他們將如何登出是很重要的。
一般來說,有三種使用案例供您考量
-
我只想執行本地登出
-
我想登出我的應用程式和 OIDC 提供者,由我的應用程式啟動
-
我想登出我的應用程式和 OIDC 提供者,由 OIDC 提供者啟動
本地登出
若要執行本地登出,不需要特殊的 OIDC 組態。Spring Security 會自動架設本地登出端點,您可以透過 logout()
DSL 進行組態。
OpenID Connect 1.0 用戶端啟動的登出
OpenID Connect Session Management 1.0 允許透過用戶端登出提供者端的最終使用者。其中一種可用的策略是 RP 啟動的登出。
如果 OpenID 提供者同時支援 Session Management 和 Discovery,則用戶端可以從 OpenID 提供者的 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 啟動登出的 OidcClientInitiatedLogoutSuccessHandler
,如下所示
-
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 Back-Channel 登出
OpenID Connect Session Management 1.0 允許透過讓提供者對用戶端進行 API 呼叫,在用戶端登出最終使用者。這稱為 OIDC Back-Channel 登出。
若要啟用此功能,您可以在 DSL 中架設 Back-Channel 登出端點,如下所示
-
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 HttpSessionEventPublisher sessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
open fun sessionEventPublisher(): HttpSessionEventPublisher {
return HttpSessionEventPublisher()
}
這將使其在呼叫 HttpSession#invalidate
時,工作階段也會從記憶體中移除。
就這樣!
這將架設端點 /logout/connect/back-channel/{registrationId}
,OIDC 提供者可以請求該端點來使您的應用程式中最終使用者的給定工作階段失效。
oidcLogout 需要同時組態 oauth2Login 。 |
oidcLogout 需要將工作階段 Cookie 命名為 JSESSIONID ,才能透過 Back-Channel 正確登出每個工作階段。 |
Back-Channel 登出架構
考量 ClientRegistration
,其識別碼為 registrationId
。
Back-Channel 登出的整體流程如下
-
在登入時,Spring Security 會在其
OidcSessionRegistry
實作中將 ID Token、CSRF Token 和提供者工作階段 ID (如果有的話) 與您應用程式的工作階段 ID 關聯起來。 -
然後在登出時,您的 OIDC 提供者會對
/logout/connect/back-channel/registrationId
進行 API 呼叫,其中包含一個登出權杖,指示要登出的sub
(最終使用者) 或sid
(提供者工作階段 ID)。 -
Spring Security 會驗證權杖的簽章和宣告。
-
如果權杖包含
sid
宣告,則只會終止與該提供者工作階段相關聯的用戶端工作階段。 -
否則,如果權杖包含
sub
宣告,則會終止該最終使用者的所有用戶端工作階段。
請記住,Spring Security 的 OIDC 支援是多租戶的。這表示它只會終止用戶端符合登出權杖中 aud 宣告的工作階段。 |
自訂 OIDC 提供者工作階段登錄
預設情況下,Spring Security 會在記憶體中儲存 OIDC 提供者工作階段與用戶端工作階段之間的所有連結。
在許多情況下,例如叢集應用程式,最好將此儲存在不同的位置,例如資料庫。
您可以透過組態自訂 OidcSessionRegistry
來達成此目的,如下所示
-
Java
-
Kotlin
@Component
public final class MySpringDataOidcSessionRegistry implements OidcSessionRegistry {
private final OidcProviderSessionRepository sessions;
// ...
@Override
public void saveSessionInformation(OidcSessionInformation info) {
this.sessions.save(info);
}
@Override
public OidcSessionInformation removeSessionInformation(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 MySpringDataOidcSessionRegistry: OidcSessionRegistry {
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(...);
}
}