OIDC ログアウト
エンドユーザーがアプリケーションにログインできるようになったら、ログアウトする方法を検討することが重要です。
一般に、考慮すべき使用例は 3 つあります。
ローカルログアウトのみを行いたい
アプリケーションと、アプリケーションによって開始された OIDC プロバイダーの両方をログアウトしたい
アプリケーションと、OIDC プロバイダーによって開始された OIDC プロバイダーの両方をログアウトしたい
ローカルログアウト
ローカルログアウトを実行するには、特別な OIDC 構成は必要ありません。Spring Security は、ローカルログアウトエンドポイントを自動的に起動します。これは、logout()
DSL を通じて構成できます。
OpenID Connect 1.0 クライアント開始のログアウト
OpenID Connect セッション管理 1.0 を使用すると、クライアントを使用してプロバイダーのエンドユーザーをログアウトできます。利用可能な戦略の 1 つは RP からのログアウト (英語) です。
OpenID プロバイダーがセッション管理とディスカバリ (英語) の両方をサポートしている場合、クライアントは OpenID プロバイダーのディスカバリメタデータ (英語) から 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 バックチャネルログアウト
OpenID Connect セッション管理 1.0 を使用すると、プロバイダーにクライアントへの API 呼び出しを行わせることで、クライアントでエンドユーザーをログアウトできます。これは OIDC バックチャネルログアウト (英語) と呼ばれます。
これを有効にするには、次のように DSL でバックチャネルログアウトエンドポイントを立ち上げます。
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()
}
以上です!
これにより、OIDC プロバイダーがアプリケーション内のエンドユーザーの特定のセッションを無効にするようにリクエストできるエンドポイント /logout/connect/back-channel/{registrationId}
が起動します。
oidcLogout を使用するには、oauth2Login も構成する必要があります。 |
oidcLogout では、バックチャネル経由で各セッションを正しくログアウトするために、セッション Cookie を JSESSIONID と呼ぶ必要があります。 |
バックチャネルログアウトアーキテクチャ
識別子が registrationId
である ClientRegistration
について考えてみましょう。
バックチャネルログアウトの全体的なフローは次のようになります。
ログイン時に、Spring Security は、ID トークン、CSRF トークン、プロバイダーセッション ID (存在する場合) を、
ReactiveOidcSessionRegistry
実装内のアプリケーションのセッション ID に関連付けます。その後、ログアウト時に、OIDC プロバイダーは、
sub
(エンドユーザー) またはsid
(プロバイダーセッション ID) がログアウトすることを示すログアウトトークンを含む/logout/connect/back-channel/registrationId
への API 呼び出しを行います。Spring Security は、トークンの署名とクレームを検証します。
トークンに
sid
クレームが含まれている場合、そのプロバイダーセッションに関連するクライアントのセッションのみが終了します。それ以外の場合、トークンに
sub
クレームが含まれている場合は、そのエンドユーザーに対するすべてのクライアントのセッションが終了します。
Spring Security の OIDC サポートはマルチテナントであることに注意してください。これは、クライアントがログアウトトークンの aud クレームに一致するセッションのみを終了することを意味します。 |
OIDC プロバイダーセッションレジストリのカスタマイズ
デフォルトでは、Spring Security は、OIDC プロバイダーセッションとクライアントセッション間のすべてのリンクをメモリ内に保存します。
クラスター化されたアプリケーションなど、これをデータベースなどの別の場所に保存した方がよい状況は数多くあります。
これは、次のようにカスタム 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(...);
}
}