对于最新的稳定版本,请使用 Spring Security 6.5.3spring-doc.cadn.net.cn

执行单次注销

Spring Security 附带支持 RP 和 AP 启动的 SAML 2.0 单次注销。spring-doc.cadn.net.cn

简而言之,Spring Security 支持两个用例:spring-doc.cadn.net.cn

  • RP-Initiated - 您的应用程序有一个端点,当 POST 到 该端点时,将注销用户并发送saml2:LogoutRequest到主张方。 此后,断言方将发回saml2:LogoutResponse并允许应用程序响应spring-doc.cadn.net.cn

  • AP 启动 - 您的应用程序具有一个终结点,该终结点将接收saml2:LogoutRequest来自主张方。 您的应用程序将在此时完成注销,然后发送saml2:LogoutResponse到主张方。spring-doc.cadn.net.cn

AP 启动方案中,应用程序在注销后执行的任何本地重定向都变得毫无意义。 一旦您的应用程序发送了saml2:LogoutResponse,它不再控制浏览器。

单次注销的最小配置

要使用 Spring Security 的 SAML 2.0 单次注销功能,您需要以下内容:spring-doc.cadn.net.cn

  • 首先,断言方必须支持 SAML 2.0 单次注销spring-doc.cadn.net.cn

  • 其次,应将断言方配置为签名和 POSTsaml2:LogoutRequests 和saml2:LogoutResponse应用程序的/logout/saml2/slo端点spring-doc.cadn.net.cn

  • 第三,应用程序必须具有 PKCS#8 私钥和 X.509 证书才能进行签名saml2:LogoutRequests 和saml2:LogoutResponsesspring-doc.cadn.net.cn

可以从初始最小示例开始,添加以下配置:spring-doc.cadn.net.cn

@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。 然后,您的应用程序将执行以下作:spring-doc.cadn.net.cn

  1. 注销用户并使会话失效spring-doc.cadn.net.cn

  2. 使用Saml2LogoutRequestResolver创建、签名和序列化<saml2:LogoutRequest>基于RelyingPartyRegistration与当前登录的用户相关联。spring-doc.cadn.net.cn

  3. 根据RelyingPartyRegistrationspring-doc.cadn.net.cn

  4. 反序列化、验证和处理<saml2:LogoutResponse>由主张方发送spring-doc.cadn.net.cn

  5. 重定向到任何已配置的成功注销端点spring-doc.cadn.net.cn

此外,当断言方发送<saml2:LogoutRequest>/logout/saml2/slo:spring-doc.cadn.net.cn

  1. 使用Saml2LogoutRequestHandler反序列化、验证和处理<saml2:LogoutRequest>由主张方发送spring-doc.cadn.net.cn

  2. 注销用户并使会话失效spring-doc.cadn.net.cn

  3. 创建、签名和序列化<saml2:LogoutResponse>基于RelyingPartyRegistration与刚刚注销的用户相关联spring-doc.cadn.net.cn

  4. 根据RelyingPartyRegistrationspring-doc.cadn.net.cn

添加saml2Logout向服务提供商添加注销功能。 因为它是一项可选功能,所以您需要为每个人启用它RelyingPartyRegistration. 您可以通过设置RelyingPartyRegistration.Builder#singleLogoutServiceLocation财产。

配置注销端点

不同的终结点可以触发三种行为:spring-doc.cadn.net.cn

  • RP 发起的注销,允许经过身份验证的用户POST并通过向断言方发送一个<saml2:LogoutRequest>spring-doc.cadn.net.cn

  • AP 发起的注销,允许断言方发送<saml2:LogoutRequest>前往应用spring-doc.cadn.net.cn

  • AP 注销响应,允许断言方发送<saml2:LogoutResponse>以响应 RP 发起的<saml2:LogoutRequest>spring-doc.cadn.net.cn

第一个是通过执行正常POST /logout当主体类型为Saml2AuthenticatedPrincipal.spring-doc.cadn.net.cn

第二个是通过 POST 触发的/logout/saml2/sloendpoint 的SAMLRequest由主张方签署。spring-doc.cadn.net.cn

第三个是由 POST 触发的/logout/saml2/sloendpoint 的SAMLResponse由主张方签署。spring-doc.cadn.net.cn

由于用户已登录或原始注销请求已知,因此registrationId已经知道了。 出于这个原因,{registrationId}默认情况下不属于这些 URL。spring-doc.cadn.net.cn

此 URL 可在 DSL 中自定义。spring-doc.cadn.net.cn

例如,如果您要将现有的信赖方迁移到 Spring Security,则您的断言方可能已经指向GET /SLOService.saml2. 若要减少断言方的配置更改,可以在 DSL 中配置筛选器,如下所示:spring-doc.cadn.net.cn

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

您还应该在RelyingPartyRegistration.spring-doc.cadn.net.cn

定制<saml2:LogoutRequest>分辨率

通常需要在<saml2:LogoutRequest>比 Spring Security 提供的默认值。spring-doc.cadn.net.cn

默认情况下,Spring Security 将发出<saml2:LogoutRequest>并供应:spring-doc.cadn.net.cn

要添加其他值,您可以使用委托,如下所示:spring-doc.cadn.net.cn

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

然后,您可以提供您的自定义Saml2LogoutRequestResolver在 DSL 中,如下所示:spring-doc.cadn.net.cn

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

定制<saml2:LogoutResponse>分辨率

通常需要在<saml2:LogoutResponse>比 Spring Security 提供的默认值。spring-doc.cadn.net.cn

默认情况下,Spring Security 将发出<saml2:LogoutResponse>并供应:spring-doc.cadn.net.cn

要添加其他值,您可以使用委托,如下所示:spring-doc.cadn.net.cn

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

然后,您可以提供您的自定义Saml2LogoutResponseResolver在 DSL 中,如下所示:spring-doc.cadn.net.cn

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

定制<saml2:LogoutRequest>认证

要自定义验证,您可以实现自己的验证Saml2LogoutRequestValidator. 此时,验证是最小的,因此您可以首先委托给默认值Saml2LogoutRequestValidator这样:spring-doc.cadn.net.cn

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

然后,您可以提供您的自定义Saml2LogoutRequestValidator在 DSL 中,如下所示:spring-doc.cadn.net.cn

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

定制<saml2:LogoutResponse>认证

要自定义验证,您可以实现自己的验证Saml2LogoutResponseValidator. 此时,验证是最小的,因此您可以首先委托给默认值Saml2LogoutResponseValidator这样:spring-doc.cadn.net.cn

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

然后,您可以提供您的自定义Saml2LogoutResponseValidator在 DSL 中,如下所示:spring-doc.cadn.net.cn

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

定制<saml2:LogoutRequest>存储

当应用程序发送<saml2:LogoutRequest>,则该值存储在会话中,以便RelayState参数和InResponseTo属性中的<saml2:LogoutResponse>可以验证。spring-doc.cadn.net.cn

如果你想将注销请求存储在会话以外的某个地方,你可以在 DSL 中提供你的自定义实现,如下所示:spring-doc.cadn.net.cn

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