最新の安定バージョンについては、Spring Security 6.3.1 を使用してください!

シングルログアウトの実行

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 証明書が必要です。

最初の最小限の例から始めて、次の構成を追加できます。

@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")
            .registrationId("id")
            .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")
            .signingX509Credentials((signing) -> signing.add(credential)) (1)
            .build();
    return new InMemoryRelyingPartyRegistrationRepository(registration);
}

@Bean
SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        .saml2Login(withDefaults())
        .saml2Logout(withDefaults()); (2)

    return http.build();
}
1- まず、署名キーを RelyingPartyRegistration インスタンスまたは複数のインスタンスに追加します
2- 次に、アプリケーションが SAMLSLO を使用してエンドユーザーをログアウトすることを希望していることを示します

ランタイムの期待

上記の構成では、ログインしているユーザーは誰でも POST /logout をアプリケーションに送信して、RP によって開始される SLO を実行できます。その後、アプリケーションは次のことを行います。

  1. ユーザーをログアウトし、セッションを無効にします

  2. Saml2LogoutRequestResolver を使用して、現在ログインしているユーザーに関連付けられている RelyingPartyRegistration に基づいて、<saml2:LogoutRequest> を作成、署名、直列化します。

  3. RelyingPartyRegistration に基づいて、アサート側にリダイレクトまたは投稿を送信します

  4. アサート側から送信された <saml2:LogoutResponse> を逆直列化し、検証し、処理します

  5. 構成済みの正常なログアウトエンドポイントにリダイレクトします

また、アサート側が <saml2:LogoutRequest> を /logout/saml2/slo に送信すると、アプリケーションは AP が開始するログアウトに参加できます。

  1. Saml2LogoutRequestHandler を使用して、アサート側から送信された <saml2:LogoutRequest> を逆直列化し、検証し、処理します。

  2. ユーザーをログアウトし、セッションを無効にします

  3. ログアウトしたばかりのユーザーに関連付けられた RelyingPartyRegistration に基づいて、<saml2:LogoutResponse> を作成、署名、直列化します。

  4. RelyingPartyRegistration に基づいて、アサート側にリダイレクトまたは投稿を送信します

saml2Logout を追加すると、サービスプロバイダーにログアウトする機能が追加されます。これはオプション機能であるため、個々の RelyingPartyRegistration ごとに有効にする必要があります。これを行うには、RelyingPartyRegistration.Builder#singleLogoutServiceLocation プロパティを設定します。

ログアウトエンドポイントの構成

さまざまなエンドポイントによってトリガーされる可能性のある動作は 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

http
    .saml2Logout((saml2) -> saml2
        .logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
        .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
    );

また、RelyingPartyRegistration でこれらのエンドポイントを構成する必要があります。

<saml2:LogoutRequest> 解決のカスタマイズ

Spring Security が提供するデフォルト以外の値を <saml2:LogoutRequest> に設定する必要があるのが一般的です。

デフォルトでは、Spring Security は <saml2:LogoutRequest> を発行し、以下を提供します。

  • Destination 属性 - RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation から

  • ID 属性 -GUID

  • <Issuer> 要素 - RelyingPartyRegistration#getEntityId から

  • <NameID> 要素 - Authentication#getName から

他の値を追加するには、次のように委譲を使用できます。

@Bean
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationResolver registrationResolver) {
	OpenSaml4LogoutRequestResolver logoutRequestResolver
			new OpenSaml4LogoutRequestResolver(registrationResolver);
	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;
}

次に、次のように DSL でカスタム Saml2LogoutRequestResolver を提供できます。

http
    .saml2Logout((saml2) -> saml2
        .logoutRequest((request) -> request
            .logoutRequestResolver(this.logoutRequestResolver)
        )
    );

<saml2:LogoutResponse> 解決のカスタマイズ

Spring Security が提供するデフォルト以外の値を <saml2:LogoutResponse> に設定する必要があるのが一般的です。

デフォルトでは、Spring Security は <saml2:LogoutResponse> を発行し、以下を提供します。

  • Destination 属性 - RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation から

  • ID 属性 -GUID

  • <Issuer> 要素 - RelyingPartyRegistration#getEntityId から

  • <Status> 要素 - SUCCESS

他の値を追加するには、次のように委譲を使用できます。

@Bean
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationResolver registrationResolver) {
	OpenSaml4LogoutResponseResolver logoutRequestResolver =
			new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
	logoutRequestResolver.setParametersConsumer((parameters) -> {
		if (checkOtherPrevailingConditions(parameters.getRequest())) {
			parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
		}
	});
	return logoutRequestResolver;
}

次に、次のように DSL でカスタム Saml2LogoutResponseResolver を提供できます。

http
    .saml2Logout((saml2) -> saml2
        .logoutRequest((request) -> request
            .logoutRequestResolver(this.logoutRequestResolver)
        )
    );

<saml2:LogoutRequest> 認証のカスタマイズ

検証をカスタマイズするために、独自の Saml2LogoutRequestValidator を実装できます。こでは、検証は最小限であるため、最初に次のようにデフォルトの Saml2LogoutRequestValidator に委譲できる場合があります。

@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
    }
}

次に、次のように DSL でカスタム Saml2LogoutRequestValidator を提供できます。

http
    .saml2Logout((saml2) -> saml2
        .logoutRequest((request) -> request
            .logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)
        )
    );

<saml2:LogoutResponse> 認証のカスタマイズ

検証をカスタマイズするために、独自の Saml2LogoutResponseValidator を実装できます。こでは、検証は最小限であるため、最初に次のようにデフォルトの Saml2LogoutResponseValidator に委譲できる場合があります。

@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
    }
}

次に、次のように DSL でカスタム Saml2LogoutResponseValidator を提供できます。

http
    .saml2Logout((saml2) -> saml2
        .logoutResponse((response) -> response
            .logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
        )
    );

<saml2:LogoutRequest> ストレージのカスタマイズ

アプリケーションが <saml2:LogoutRequest> を送信すると、値がセッションに格納されるため、RelayState パラメーターと <saml2:LogoutResponse> の InResponseTo 属性を検証できます。

セッション以外の場所にログアウトリクエストを保存する場合は、次のように DSL でカスタム実装を提供できます。

http
    .saml2Logout((saml2) -> saml2
        .logoutRequest((request) -> request
            .logoutRequestRepository(myCustomLogoutRequestRepository)
        )
    );