# 执行单注销

Spring 安全性支持RP和AP发起的SAML2.0单注销。

简单地说,有两个用例 Spring 安全支持:

  • **RP-initiated **-你的应用程序有一个端点,当发送到该端点时,该端点将注销该用户并向断言的一方发送saml2:LogoutRequest。在此之后,断言方会返回一个saml2:LogoutResponse并允许你的应用程序进行响应

  • **AP-initied **-你的应用程序有一个端点,它将从主张方接收saml2:LogoutRequest。你的应用程序将在此时完成注销,然后向断言的一方发送saml2:LogoutResponse

在**AP-initied **场景中,应用程序在注销后进行的任何本地重定向都是没有意义的。
一旦应用程序发送了saml2:LogoutResponse,它就不再具有对浏览器的控制权。

# 单注销的最小配置

要使用 Spring Security的SAML2.0单注销功能,你将需要以下功能:

  • 首先,断言一方必须支持SAML2.0单注销

  • 其次,应该将断言一方配置为签名并发布saml2:LogoutRequests和saml2:LogoutResponses你的应用程序的/logout/saml2/slo端点

  • 第三,你的应用程序必须具有用于签名saml2:LogoutRequests和saml2:LogoutResponses的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 -其次,指示你的应用程序想要使用SAML SLO注销最终用户

# 运行时期望

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

  1. 注销用户并使其无效会话

  2. 使用Saml2LogoutRequestResolver来创建、签名和序列化基于与当前登录用户关联的[RelyingPartyRegistration](login/overview.html# Servlet-saml2login-relyingpartyregistration)的<saml2:LogoutRequest>

  3. 根据[RelyingPartyRegistration](login/overview.html# Servlet-saml2login-relyingpartyregistration)向断言的一方发送重定向或帖子

  4. 反序列化、验证和处理由断言方发送的<saml2:LogoutResponse>

  5. 重定向到任何配置成功的注销端点

此外,当断言一方向<saml2:LogoutRequest>发送/logout/saml2/slo时,你的应用程序可以参与AP发起的注销:

  1. 使用Saml2LogoutRequestHandler来反序列化、验证和处理由主张方发送的<saml2:LogoutRequest>

  2. 注销用户并使其无效会话

  3. 基于与刚刚注销的用户关联的[RelyingPartyRegistration](login/overview.html# Servlet-saml2login-relyingpartyregistration),创建、签名和序列化<saml2:LogoutResponse>

  4. 基于[RelyingPartyRegistration](login/overview.html# Servlet-saml2login-relyingpartyregistration)向断言方发一个重定向或发布

添加saml2Logout会将注销的功能添加到服务提供程序。
由于它是一种可选功能,因此你需要为每个单独的RelyingPartyRegistration启用它。
你可以通过设置RelyingPartyRegistration.Builder#singleLogoutServiceLocation属性来实现这一点。

# 配置注销端点

不同的端点可以触发三种行为:

  • RP-initiated logout,它允许经过身份验证的用户POST,并通过向断言的一方发送<saml2:LogoutRequest>来触发注销过程。

  • AP发起的注销,它允许断言的一方向应用程序发送<saml2:LogoutRequest>

  • AP注销响应,它允许断言一方发送<saml2:LogoutResponse>作为对RP发起的<saml2:LogoutRequest>的响应

第一种是当主体类型Saml2AuthenticatedPrincipal时,执行正常的POST /logout触发。

第二种是通过发送到带有由主张方签名的SAMLRequest/logout/saml2/slo端点来触发的。

第三种是通过发送到带有由主张方签名的SAMLResponse/logout/saml2/slo端点来触发的。

因为用户已经登录或者原始注销请求是已知的,所以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>分辨率

通常需要在<saml2:LogoutRequest>中设置其他值,而不是 Spring Security提供的默认值。

默认情况下, 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>分辨率

通常需要在<saml2:LogoutResponse>中设置其他值,而不是 Spring Security提供的默认值。

默认情况下, 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参数和InResponseTo属性中的<saml2:LogoutResponse>可以被验证。

如果你希望将注销请求存储在会话以外的某个地方,那么可以在DSL中提供你的自定义实现,如下所示:

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

SAML2身份验证响应SAML2元数据