Performing Single Logout

Spring Security 附带了对 RP- 和 AP- 发起的 SAML 2.0 单点注销的支持。

Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout.

简而言之,Spring Security 支持以下两种用例:

Briefly, there are two use cases Spring Security supports:

  • RP-Initiated - Your application has an endpoint that, when POSTed to, will logout the user and send a saml2:LogoutRequest to the asserting party. Thereafter, the asserting party will send back a saml2:LogoutResponse and allow your application to respond

  • AP-Initiated - Your application has an endpoint that will receive a saml2:LogoutRequest from the asserting party. Your application will complete its logout at that point and then send a saml2:LogoutResponse to the asserting party.

AP-Initiated 场景中,应用程序在退出后将要执行的任何本地重定向都将变得毫无意义。一旦应用程序发送 saml2:LogoutResponse,它将不再控制浏览器。

In the AP-Initiated scenario, any local redirection that your application would do post-logout is rendered moot. Once your application sends a saml2:LogoutResponse, it no longer has control of the browser.

Minimal Configuration for Single Logout

要使用 Spring Security 的 SAML 2.0 单点注销功能,您需要以下内容:

To use Spring Security’s SAML 2.0 Single Logout feature, you will need the following things:

  • First, the asserting party must support SAML 2.0 Single Logout

  • Second, the asserting party should be configured to sign and POST saml2:LogoutRequest s and saml2:LogoutResponse s your application’s /logout/saml2/slo endpoint

  • Third, your application must have a PKCS#8 private key and X.509 certificate for signing saml2:LogoutRequest s and saml2:LogoutResponse s

您可以从初始最小示例开始,并添加以下配置:

You can begin from the initial minimal example and add the following configuration:

@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 - First, add your signing key to the RelyingPartyRegistration instance or to multiple instances
2 - Second, indicate that your application wants to use SAML SLO to logout the end user

Runtime Expectations

给定以上配置,任何已登录的用户都可以向您的应用程序发送 POST /logout 来执行 RP 发起的 SLO。然后,您的应用程序将执行以下操作:

Given the above configuration any logged in user can send a POST /logout to your application to perform RP-initiated SLO. Your application will then do the following:

  1. Logout the user and invalidate the session

  2. Use a Saml2LogoutRequestResolver to create, sign, and serialize a <saml2:LogoutRequest> based on the RelyingPartyRegistration associated with the currently logged-in user.

  3. Send a redirect or post to the asserting party based on the RelyingPartyRegistration

  4. Deserialize, verify, and process the <saml2:LogoutResponse> sent by the asserting party

  5. Redirect to any configured successful logout endpoint

此外,如果断言方向 /logout/saml2/slo 发送 <saml2:LogoutRequest> 时,您的应用程序也可以参与 AP 发起的注销操作:

Also, your application can participate in an AP-initiated logout when the asserting party sends a <saml2:LogoutRequest> to /logout/saml2/slo:

  1. Use a Saml2LogoutRequestHandler to deserialize, verify, and process the <saml2:LogoutRequest> sent by the asserting party

  2. Logout the user and invalidate the session

  3. Create, sign, and serialize a <saml2:LogoutResponse> based on the RelyingPartyRegistration associated with the just logged-out user

  4. Send a redirect or post to the asserting party based on the RelyingPartyRegistration

添加 saml2Logout 会为服务提供者增加退出功能。由于这是一个可选功能,您需要为每个单独的 RelyingPartyRegistration 启用该功能。您可以通过设置 RelyingPartyRegistration.Builder#singleLogoutServiceLocation 属性来实现。

Adding saml2Logout adds the capability for logout to the service provider. Because it is an optional capability, you need to enable it for each individual RelyingPartyRegistration. You can do this by setting the RelyingPartyRegistration.Builder#singleLogoutServiceLocation property.

Configuring Logout Endpoints

有三个行为可由不同的端点触发:

There are three behaviors that can be triggered by different endpoints:

  • RP-initiated logout, which allows an authenticated user to POST and trigger the logout process by sending the asserting party a <saml2:LogoutRequest>

  • AP-initiated logout, which allows an asserting party to send a <saml2:LogoutRequest> to the application

  • AP logout response, which allows an asserting party to send a <saml2:LogoutResponse> in response to the RP-initiated <saml2:LogoutRequest>

第一个是在主体类型为 Saml2AuthenticatedPrincipal 时执行常规 POST /logout 触发。

The first is triggered by performing normal POST /logout when the principal is of type Saml2AuthenticatedPrincipal.

第二个由用宣告方签名的 SAMLRequest POST 到 /logout/saml2/slo 终结点触发。

The second is triggered by POSTing to the /logout/saml2/slo endpoint with a SAMLRequest signed by the asserting party.

第三个由用宣告方签名的 SAMLResponse POST 到 /logout/saml2/slo 终结点触发。

The third is triggered by POSTing to the /logout/saml2/slo endpoint with a SAMLResponse signed by the asserting party.

因为用户已经登陆或原始的登出请求是已知的,所以 registrationId 也是已知的。出于这个原因,{registrationId} 默认情况下不包含在这些 URL 中。

Because the user is already logged in or the original Logout Request is known, the registrationId is already known. For this reason, {registrationId} is not part of these URLs by default.

此 URL 在 DSL 中可以自定义。

This URL is customizable in the DSL.

例如,如果你将你现有的依赖方迁移到 Spring Security 上,那么你的宣告方可能已经指向 GET /SLOService.saml2。为了减少宣告方配置上的更改,你可以在 DSL 中如此配置过滤器:

For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to GET /SLOService.saml2. To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so:

  • Java

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

你还应在你的 RelyingPartyRegistration 中配置这些终结点。

You should also configure these endpoints in your RelyingPartyRegistration.

Customizing <saml2:LogoutRequest> Resolution

通常需要在 <saml2:LogoutRequest> 中设置除 Spring Security 提供的默认值之外的其他值。

It’s common to need to set other values in the <saml2:LogoutRequest> than the defaults that Spring Security provides.

默认情况下,Spring Security 将发出 <saml2:LogoutRequest> 并提供:

By default, Spring Security will issue a <saml2:LogoutRequest> and supply:

  • The Destination attribute - from RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation

  • The ID attribute - a GUID

  • The <Issuer> element - from RelyingPartyRegistration#getEntityId

  • The <NameID> element - from Authentication#getName

若要添加其他值,你可以使用委托,如下所示:

To add other values, you can use delegation, like so:

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

然后,你可以如此在 DSL 中提供你的自定义 Saml2LogoutRequestResolver

Then, you can supply your custom Saml2LogoutRequestResolver in the DSL as follows:

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

Customizing <saml2:LogoutResponse> Resolution

通常需要在 <saml2:LogoutResponse> 中设置除 Spring Security 提供的默认值之外的其他值。

It’s common to need to set other values in the <saml2:LogoutResponse> than the defaults that Spring Security provides.

默认情况下,Spring Security 将发出 <saml2:LogoutResponse> 并提供:

By default, Spring Security will issue a <saml2:LogoutResponse> and supply:

  • The Destination attribute - from RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation

  • The ID attribute - a GUID

  • The <Issuer> element - from RelyingPartyRegistration#getEntityId

  • The <Status> element - SUCCESS

若要添加其他值,你可以使用委托,如下所示:

To add other values, you can use delegation, like so:

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

然后,你可以如此在 DSL 中提供你的自定义 Saml2LogoutResponseResolver

Then, you can supply your custom Saml2LogoutResponseResolver in the DSL as follows:

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

Customizing <saml2:LogoutRequest> Authentication

若要自定义验证,你可以实现你自己的 Saml2LogoutRequestValidator。此时,验证是最低限度的,因此你可能可以先如此委托到默认的 Saml2LogoutRequestValidator

To customize validation, you can implement your own Saml2LogoutRequestValidator. At this point, the validation is minimal, so you may be able to first delegate to the default Saml2LogoutRequestValidator like so:

@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

Then, you can supply your custom Saml2LogoutRequestValidator in the DSL as follows:

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

Customizing <saml2:LogoutResponse> Authentication

若要自定义验证,你可以实现你自己的 Saml2LogoutResponseValidator。此时,验证是最低限度的,因此你可能可以先如此委托到默认的 Saml2LogoutResponseValidator

To customize validation, you can implement your own Saml2LogoutResponseValidator. At this point, the validation is minimal, so you may be able to first delegate to the default Saml2LogoutResponseValidator like so:

@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

Then, you can supply your custom Saml2LogoutResponseValidator in the DSL as follows:

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

Customizing <saml2:LogoutRequest> storage

当你的应用发送 <saml2:LogoutRequest> 时,该值储存在会话中,以便可以验证 RelayState 参数和 <saml2:LogoutResponse> 中的 InResponseTo 属性。

When your application sends a <saml2:LogoutRequest>, the value is stored in the session so that the RelayState parameter and the InResponseTo attribute in the <saml2:LogoutResponse> can be verified.

如果你想将在会话之外的某个地方存储登出请求,你可以如此在 DSL 中提供你的自定义实现:

If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:

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