シングルログアウトの実行
Spring Security には、その他のログアウトメカニズムの中でも、RP および AP によって開始される SAML 2.0 シングルログアウトのサポートが付属しています。
簡単に言うと、Spring Security がサポートする 2 つのユースケースがあります。
RP 開始 - アプリケーションには、POST されると、ユーザーをログアウトして
saml2:LogoutRequest
をアサート側に送信するエンドポイントがあります。その後、アサート側はsaml2:LogoutResponse
を送り返し、アプリケーションが応答できるようにします。AP-Initiated- アプリケーションには、アサート側から
saml2:LogoutRequest
を受信するエンドポイントがあります。アプリケーションはその時点でログアウトを完了し、saml2:LogoutResponse
をアサート側に送信します。
AP が開始するシナリオでは、アプリケーションがログアウト後に実行するローカルリダイレクトは無効になります。アプリケーションが saml2:LogoutResponse を送信すると、ブラウザーを制御できなくなります。 |
シングルログアウトの最小構成
Spring Security の SAML 2.0 シングルログアウト機能を使用するには、次のものが必要です。
まず、アサート側は SAML 2.0 シングルログアウトをサポートする必要があります
次に、アサート側は、アプリケーションの
/logout/saml2/slo
エンドポイントにsaml2:LogoutRequest
とsaml2:LogoutResponse
に署名して POST するように構成する必要があります。第 3 に、アプリケーションには、
saml2:LogoutRequest
とsaml2:LogoutResponse
に署名するための PKCS#8 秘密鍵と X.509 証明書が必要です。
Spring Boot では、次の方法でこれを実現できます。
spring:
security:
saml2:
relyingparty:
registration:
metadata:
signing.credentials: (3)
- private-key-location: classpath:credentials/rp-private.key
certificate-location: classpath:credentials/rp-certificate.crt
singlelogout.url: "{baseUrl}/logout/saml2/slo" (2)
assertingparty:
metadata-uri: https://ap.example.com/metadata (1)
1 | - IDP のメタデータ URI。アプリケーションに SLO のサポートを示す |
2 | - アプリケーションの SLO エンドポイント |
3 | - <saml2:LogoutRequest> および <saml2:LogoutResponse> に署名するための署名資格情報 |
An asserting party supports Single Logout if their metadata includes the `<SingleLogoutService>` element in their metadata.
以上です!
Spring Security のログアウトサポートには、いくつかの構成ポイントがあります。次のユースケースを検討してください。
上記の最小限の構成がどのように機能するかを理解する
全体的なアーキテクチャを把握する
ユーザーがアプリからのみログアウトできるようにする
ログアウトエンドポイントをカスタマイズする
<saml2:LogoutRequests>
をセッション以外の場所に保存する
スタートアップの期待
これらのプロパティを使用すると、ログインに加えて、SAML 2.0 サービスプロバイダーは、RP または AP によって開始されるログアウトを使用して、<saml2:LogoutRequest>
および <saml2:LogoutResponse>
によるログアウトを容易にするように自動的に構成されます。
これは、決定論的な起動プロセスを通じてこれを実現します。
Identity Server メタデータエンドポイントで
<SingleLogoutService>
要素を照会するメタデータをスキャンし、公開署名検証キーをキャッシュする
適切なエンドポイントを準備する
このプロセスの結果、サービスプロバイダーが正常に起動するには、アイデンティティサーバーが起動してリクエストを受信する必要があります。
サービスプロバイダーが ID サーバーにクエリを実行したときに ID サーバーがダウンしている場合 (適切なタイムアウトが指定されている場合)、起動は失敗します。 |
ランタイムの期待
上記の構成では、ログインしているユーザーは誰でもアプリケーションに POST /logout
を送信して、RP 開始 SLO を実行できます。アプリケーションは次のことを実行します。
ユーザーをログアウトし、セッションを無効にします
<saml2:LogoutRequest>
を生成し、関連するアサーションパーティの SLO エンドポイントに POST する次に、アサーション側が
<saml2:LogoutResponse>
で応答した場合、アプリケーションはそれを検証し、設定された成功エンドポイントにリダイレクトします。
また、アサーション側が <saml2:LogoutRequest>
を /logout/saml2/slo
に送信すると、アプリケーションは AP 開始ログアウトに参加できます。この場合、アプリケーションは次の処理を実行します。
<saml2:LogoutRequest>
を確認するユーザーをログアウトし、セッションを無効にします
<saml2:LogoutResponse>
を生成し、それをアサーション側の SLO エンドポイントに POST する
Boot なしの最小構成
Boot プロパティの代わりに、次のように Bean を直接公開することで同じ結果を実現することもできます。
Java
Kotlin
@Configuration
public class SecurityConfig {
@Value("${private.key}") RSAPrivateKey key;
@Value("${public.certificate}") X509Certificate certificate;
@Bean
RelyingPartyRegistrationRepository registrations() {
Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata") (1)
.registrationId("metadata")
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") (2)
.signingX509Credentials((signing) -> signing.add(credential)) (3)
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.saml2Login(withDefaults())
.saml2Logout(withDefaults()); (4)
return http.build();
}
}
@Configuration
class SecurityConfig(@Value("${private.key}") val key: RSAPrivateKey,
@Value("${public.certificate}") val certificate: X509Certificate) {
@Bean
fun registrations(): RelyingPartyRegistrationRepository {
val credential = Saml2X509Credential.signing(key, certificate)
val registration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata") (1)
.registrationId("metadata")
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") (2)
.signingX509Credentials({ signing: List<Saml2X509Credential> -> signing.add(credential) }) (3)
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
@Bean
fun web(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
anyRequest = authenticated
}
saml2Login {
}
saml2Logout { (4)
}
}
return http.build()
}
}
1 | - IDP のメタデータ URI。アプリケーションに SLO のサポートを示す |
2 | - アプリケーションの SLO エンドポイント |
3 | - <saml2:LogoutRequest> および <saml2:LogoutResponse> に署名するための署名資格情報。複数の証明書利用者に追加することもできます。 |
4 | - 次に、アプリケーションが SAMLSLO を使用してエンドユーザーをログアウトすることを希望していることを示します |
saml2Logout を追加すると、サービスプロバイダー全体にログアウト機能が追加されます。これはオプションの機能であるため、個々の RelyingPartyRegistration に対して有効にする必要があります。これを行うには、上記のように RelyingPartyRegistration.Builder#singleLogoutServiceLocation プロパティを設定します。 |
Saml 2.0 ログアウトの仕組み
次に、先ほど見たようなサーブレットベースのアプリケーションで Spring Security が SAML 2.0 ログアウト (英語) をサポートするために使用するアーキテクチャコンポーネントを見てみましょう。
RP によるログアウトの場合:
Spring Security はログアウトフローを実行し、LogoutHandler
を呼び出してセッションを無効化し、その他のクリーンアップを実行します。次に、Saml2RelyingPartyInitiatedLogoutSuccessHandler
(Javadoc) を呼び出します。
ログアウト成功ハンドラーは、Saml2LogoutRequestResolver
(Javadoc) のインスタンスを使用して、<saml2:LogoutRequest>
を作成、署名、直列化します。現在の Saml2AuthenticatedPrincipal
に関連付けられている RelyingPartyRegistration
のキーと構成を使用します。次に、<saml2:LogoutRequest>
をアサーションパーティの SLO エンドポイントにリダイレクト POST します。
ブラウザーは制御をアサーション側に引き渡します。アサーション側がリダイレクトした場合(そうならない可能性もあります)、アプリケーションはステップに進みます。。
Saml2LogoutResponseFilter
(Javadoc) は、Saml2LogoutResponseValidator
(Javadoc) を使用して <saml2:LogoutResponse>
をデシリアライズ、検証、処理します。
有効な場合は、/login?logout
または設定されているものにリダイレクトして、ローカルログアウトフローを完了します。無効な場合は、400 で応答します。
AP によるログアウトの場合:
Saml2LogoutRequestFilter
(Javadoc) は、Saml2LogoutRequestValidator
(Javadoc) を使用して <saml2:LogoutRequest>
をデシリアライズ、検証、処理します。
有効な場合、フィルターは構成された LogoutHandler
を呼び出し、セッションを無効にしてその他のクリーンアップを実行します。
Saml2LogoutResponseResolver
(Javadoc) を使用して、<saml2:LogoutResponse>
を作成、署名、直列化します。エンドポイントまたは <saml2:LogoutRequest>
の内容から派生した RelyingPartyRegistration
のキーと構成を使用します。次に、<saml2:LogoutResponse>
をアサーションパーティの SLO エンドポイントにリダイレクト POST します。
ブラウザーはアサーション側に制御を渡します。
無効な場合は 400 で応答します [GitHub] (英語) 。
ログアウトエンドポイントの構成
さまざまなエンドポイントによってトリガーされる可能性のある動作は 3 つあります。
RP によって開始されるログアウト。これにより、認証されたユーザーは
POST
を実行し、アサート側に<saml2:LogoutRequest>
を送信してログアウトプロセスをトリガーできます。AP が開始するログアウト。これにより、アサート側が
<saml2:LogoutRequest>
をアプリケーションに送信できるようになります。AP ログアウトレスポンス。これにより、アサート側は RP によって開始された
<saml2:LogoutRequest>
にレスポンスして<saml2:LogoutResponse>
を送信できます。
1 つ目は、プリンシパルが Saml2AuthenticatedPrincipal
型の場合に、通常の POST /logout
を実行することによってトリガーされます。
2 つ目は、アサート側によって署名された SAMLRequest
を使用して /logout/saml2/slo
エンドポイントに POST することによってトリガーされます。
3 つ目は、アサート側によって署名された SAMLResponse
を使用して /logout/saml2/slo
エンドポイントに POST することによってトリガーされます。
ユーザーがすでにログインしているか、元のログアウトリクエストがわかっているため、registrationId
はすでにわかっています。このため、{registrationId}
はデフォルトではこれらの URL の一部ではありません。
この URL は DSL でカスタマイズ可能です。
例: 既存の証明書利用者を Spring Security に移行する場合、主張する当事者はすでに GET /SLOService.saml2
を指している可能性があります。アサーティングパーティの設定の変更を減らすために、DSL で次のようにフィルターを設定できます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
);
http {
saml2Logout {
logoutRequest {
logoutUrl = "/SLOService.saml2"
}
logoutResponse {
logoutUrl = "/SLOService.saml2"
}
}
}
また、RelyingPartyRegistration
でこれらのエンドポイントを構成する必要があります。
また、次のようにローカルでログアウトをトリガーするためのエンドポイントをカスタマイズすることもできます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout"));
http {
saml2Logout {
logoutUrl = "/saml2/logout"
}
}
ローカルログアウトと SAML 2.0 ログアウトの分離
場合によっては、ローカルログアウト用に 1 つのログアウトエンドポイントを公開し、RP によって開始される SLO 用に別のログアウトエンドポイントを公開したいことがあります。他のログアウトメカニズムの場合と同様に、それぞれ異なるエンドポイントを持つ限り、複数のログアウトメカニズムを登録できます。
たとえば、DSL を次のように接続できます。
Java
Kotlin
http
.logout((logout) -> logout.logoutUrl("/logout"))
.saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout"));
http {
logout {
logoutUrl = "/logout"
}
saml2Logout {
logoutUrl = "/saml2/logout"
}
}
そして、クライアントが POST /logout
を送信すると、セッションはクリアされますが、アサーションパーティに <saml2:LogoutRequest>
は送信されません。ただし、クライアントが POST /saml2/logout
を送信すると、アプリケーションは通常どおり SAML 2.0 SLO を開始します。
<saml2:LogoutRequest>
解決のカスタマイズ
Spring Security が提供するデフォルト以外の値を <saml2:LogoutRequest>
に設定する必要があるのが一般的です。
デフォルトでは、Spring Security は <saml2:LogoutRequest>
を発行し、以下を提供します。
Destination
属性 -RelyingPartyRegistration#getAssertingPartyMetadata#getSingleLogoutServiceLocation
からID
属性 -GUID<Issuer>
要素 -RelyingPartyRegistration#getEntityId
から<NameID>
要素 -Authentication#getName
から
他の値を追加するには、次のように委譲を使用できます。
Java
Kotlin
@Bean
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
OpenSaml4LogoutRequestResolver logoutRequestResolver =
new OpenSaml4LogoutRequestResolver(registrations);
logoutRequestResolver.setParametersConsumer((parameters) -> {
String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
LogoutRequest logoutRequest = parameters.getLogoutRequest();
NameID nameId = logoutRequest.getNameID();
nameId.setValue(name);
nameId.setFormat(format);
});
return logoutRequestResolver;
}
@Bean
open fun logoutRequestResolver(registrations:RelyingPartyRegistrationRepository?): Saml2LogoutRequestResolver {
val logoutRequestResolver = OpenSaml4LogoutRequestResolver(registrations)
logoutRequestResolver.setParametersConsumer { parameters: LogoutRequestParameters ->
val name: String = (parameters.getAuthentication().getPrincipal() as Saml2AuthenticatedPrincipal).getFirstAttribute("CustomAttribute")
val format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
val logoutRequest: LogoutRequest = parameters.getLogoutRequest()
val nameId: NameID = logoutRequest.getNameID()
nameId.setValue(name)
nameId.setFormat(format)
}
return logoutRequestResolver
}
次に、次のように DSL でカスタム Saml2LogoutRequestResolver
を提供できます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestResolver(this.logoutRequestResolver)
)
);
http {
saml2Logout {
logoutRequest {
logoutRequestResolver = this.logoutRequestResolver
}
}
}
<saml2:LogoutResponse>
解決のカスタマイズ
Spring Security が提供するデフォルト以外の値を <saml2:LogoutResponse>
に設定する必要があるのが一般的です。
デフォルトでは、Spring Security は <saml2:LogoutResponse>
を発行し、以下を提供します。
Destination
属性 -RelyingPartyRegistration#getAssertingPartyMetadata#getSingleLogoutServiceResponseLocation
からID
属性 -GUID<Issuer>
要素 -RelyingPartyRegistration#getEntityId
から<Status>
要素 -SUCCESS
他の値を追加するには、次のように委譲を使用できます。
Java
Kotlin
@Bean
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
OpenSaml4LogoutResponseResolver logoutRequestResolver =
new OpenSaml4LogoutResponseResolver(registrations);
logoutRequestResolver.setParametersConsumer((parameters) -> {
if (checkOtherPrevailingConditions(parameters.getRequest())) {
parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
}
});
return logoutRequestResolver;
}
@Bean
open fun logoutResponseResolver(registrations: RelyingPartyRegistrationRepository?): Saml2LogoutResponseResolver {
val logoutRequestResolver = OpenSaml4LogoutResponseResolver(registrations)
logoutRequestResolver.setParametersConsumer { LogoutResponseParameters parameters ->
if (checkOtherPrevailingConditions(parameters.getRequest())) {
parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT)
}
}
return logoutRequestResolver
}
次に、次のように DSL でカスタム Saml2LogoutResponseResolver
を提供できます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestResolver(this.logoutRequestResolver)
)
);
http {
saml2Logout {
logoutRequest {
logoutRequestResolver = this.logoutRequestResolver
}
}
}
<saml2:LogoutRequest>
認証のカスタマイズ
検証をカスタマイズするために、独自の Saml2LogoutRequestValidator
を実装できます。こでは、検証は最小限であるため、最初に次のようにデフォルトの Saml2LogoutRequestValidator
に委譲できる場合があります。
Java
Kotlin
@Component
public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
private final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();
@Override
public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {
// verify signature, issuer, destination, and principal name
Saml2LogoutValidatorResult result = delegate.authenticate(authentication);
LogoutRequest logoutRequest = // ... parse using OpenSAML
// perform custom validation
}
}
@Component
open class MyOpenSamlLogoutRequestValidator: Saml2LogoutRequestValidator {
private val delegate = OpenSamlLogoutRequestValidator()
@Override
fun logout(parameters: Saml2LogoutRequestValidatorParameters): Saml2LogoutRequestValidator {
// verify signature, issuer, destination, and principal name
val result = delegate.authenticate(authentication)
val logoutRequest: LogoutRequest = // ... parse using OpenSAML
// perform custom validation
}
}
次に、次のように DSL でカスタム Saml2LogoutRequestValidator
を提供できます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestValidator(myOpenSamlLogoutRequestValidator)
)
);
http {
saml2Logout {
logoutRequest {
logoutRequestValidator = myOpenSamlLogoutRequestValidator
}
}
}
<saml2:LogoutResponse>
認証のカスタマイズ
検証をカスタマイズするために、独自の Saml2LogoutResponseValidator
を実装できます。こでは、検証は最小限であるため、最初に次のようにデフォルトの Saml2LogoutResponseValidator
に委譲できる場合があります。
Java
Kotlin
@Component
public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
private final Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();
@Override
public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {
// verify signature, issuer, destination, and status
Saml2LogoutValidatorResult result = delegate.authenticate(parameters);
LogoutResponse logoutResponse = // ... parse using OpenSAML
// perform custom validation
}
}
@Component
open class MyOpenSamlLogoutResponseValidator: Saml2LogoutResponseValidator {
private val delegate = OpenSaml4LogoutResponseValidator()
@Override
fun logout(parameters: Saml2LogoutResponseValidatorParameters): Saml2LogoutResponseValidator {
// verify signature, issuer, destination, and status
val result = delegate.authenticate(authentication)
val logoutResponse: LogoutResponse = // ... parse using OpenSAML
// perform custom validation
}
}
次に、次のように DSL でカスタム Saml2LogoutResponseValidator
を提供できます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2
.logoutResponse((response) -> response
.logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
)
);
http {
saml2Logout {
logoutResponse {
logoutResponseValidator = myOpenSamlLogoutResponseValidator
}
}
}
<saml2:LogoutRequest>
ストレージのカスタマイズ
アプリケーションが <saml2:LogoutRequest>
を送信すると、値がセッションに格納されるため、RelayState
パラメーターと <saml2:LogoutResponse>
の InResponseTo
属性を検証できます。
セッション以外の場所にログアウトリクエストを保存する場合は、次のように DSL でカスタム実装を提供できます。
Java
Kotlin
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestRepository(myCustomLogoutRequestRepository)
)
);
http {
saml2Logout {
logoutRequest {
logoutRequestRepository = myCustomLogoutRequestRepository
}
}
}
その他のログアウト関連の参照
セクション CSRF 警告のログアウト