生成 <saml2:AuthnRequest>

如前所述,Spring Security 的 SAML 2.0 支持会生成一个 <saml2:AuthnRequest>,以向断言方发起身份验证。spring-doc.cadn.net.cn

Spring Security 通过在过滤器链中注册 Saml2WebSsoAuthenticationRequestFilter 来部分实现这一功能。 该过滤器默认响应端点 /saml2/authenticate/{registrationId}/saml2/authenticate?registrationId={registrationId}spring-doc.cadn.net.cn

例如,如果你部署到了 rp.example.com,并且你为你的注册指定了 ID okta,那么你可以导航到:spring-doc.cadn.net.cn

结果将是一个重定向,其中包含一个 SAMLRequest 参数,该参数包含了已签名、压缩并编码的 <saml2:AuthnRequest>spring-doc.cadn.net.cn

配置<saml2:AuthnRequest>端点

若要将端点配置为与默认设置不同,您可以在 saml2Login 中设置该值:spring-doc.cadn.net.cn

@Bean
SecurityFilterChain filterChain(HttpSecurity http) {
	http
        .saml2Login((saml2) -> saml2
            .authenticationRequestUriQuery("/custom/auth/sso?peerEntityID={registrationId}")
        );
	return new CustomSaml2AuthenticationRequestRepository();
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        saml2Login {
            authenticationRequestUriQuery = "/custom/auth/sso?peerEntityID={registrationId}"
        }
    }
    return CustomSaml2AuthenticationRequestRepository()
}

改变<saml2:AuthnRequest>已存储

Saml2WebSsoAuthenticationRequestFilter 使用 Saml2AuthenticationRequestRepository 在将 AbstractSaml2AuthenticationRequest 实例持久化后,再发送 <saml2:AuthnRequest> 给断言方。spring-doc.cadn.net.cn

此外,Saml2WebSsoAuthenticationFilterSaml2AuthenticationTokenConverter 使用一个 Saml2AuthenticationRequestRepository 来加载任何 AbstractSaml2AuthenticationRequest,作为 验证 <saml2:Response> 的一部分。spring-doc.cadn.net.cn

默认情况下,Spring Security 使用 HttpSessionSaml2AuthenticationRequestRepository,它将 AbstractSaml2AuthenticationRequest 存储在 HttpSession 中。spring-doc.cadn.net.cn

如果你有一个自定义的 Saml2AuthenticationRequestRepository 实现,可以通过将其声明为 @Bean 来进行配置,如下例所示:spring-doc.cadn.net.cn

@Bean
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
	return new CustomSaml2AuthenticationRequestRepository();
}
@Bean
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
    return CustomSaml2AuthenticationRequestRepository()
}

缓存<saml2:AuthnRequest>通过 Relay State

如果你不想使用会话(session)来存储 <saml2:AuthnRequest>,也可以将其存储在分布式缓存中。 当你尝试使用 SameSite=Strict 时,这会很有帮助,因为在这种情况下,从身份提供商(Identity Provider)重定向回来时可能会丢失认证请求。spring-doc.cadn.net.cn

记住将它存储在会话(session)中具有安全优势这一点非常重要。 其中一个优势是它能自然地防御登录固定(login fixation)攻击。 例如,如果应用程序从会话中查找身份验证请求,那么即使攻击者向受害者提供他们自己的 SAML 响应,登录也会失败。spring-doc.cadn.net.cn

另一方面,如果我们依赖 InResponseTo 或 RelayState 来检索身份验证请求,那么就无法确定该 SAML 响应是否是由该握手过程所请求的。spring-doc.cadn.net.cn

为协助实现这一点,Spring Security 提供了 CacheSaml2AuthenticationRequestRepository,您可以将其发布为一个 Bean,供过滤器链自动获取:spring-doc.cadn.net.cn

@Bean
Saml2AuthenticationRequestRepository<?> authenticationRequestRepository() {
	return new CacheSaml2AuthenticationRequestRepository();
}
@Bean
fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<*> {
    return CacheSaml2AuthenticationRequestRepository()
}

改变<saml2:AuthnRequest>已发送

默认情况下,Spring Security 会对每个 <saml2:AuthnRequest> 进行签名,并以 GET 请求发送给断言方。spring-doc.cadn.net.cn

许多断言方并不要求对 <saml2:AuthnRequest> 进行签名。 这可以通过 RelyingPartyRegistrations 自动配置,也可以手动提供,如下所示:spring-doc.cadn.net.cn

不要求签名的 AuthnRequest
spring:
  security:
    saml2:
      relyingparty:
        registration:
          okta:
            assertingparty:
              entity-id: ...
              singlesignon.sign-request: false
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyMetadata((party) -> party
            // ...
            .wantAuthnRequestsSigned(false)
        )
        .build();
var relyingPartyRegistration: RelyingPartyRegistration =
    RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
                // ...
                .wantAuthnRequestsSigned(false)
        }
        .build()

否则,您需要为 RelyingPartyRegistration#signingX509Credentials 指定一个私钥,以便 Spring Security 在发送之前对 <saml2:AuthnRequest> 进行签名。spring-doc.cadn.net.cn

默认情况下,Spring Security 会使用 <saml2:AuthnRequest>rsa-sha256 进行签名,但某些身份提供方可能会在其元数据中指定要求使用不同的算法。spring-doc.cadn.net.cn

您可以基于主张方的元数据使用RelyingPartyRegistrations来配置算法。spring-doc.cadn.net.cn

或者,您可以手动提供它:spring-doc.cadn.net.cn

String metadataLocation = "classpath:asserting-party-metadata.xml";
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
        // ...
        .assertingPartyMetadata((party) -> party
            // ...
            .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
        )
        .build();
var metadataLocation = "classpath:asserting-party-metadata.xml"
var relyingPartyRegistration: RelyingPartyRegistration =
    RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
        // ...
        .assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
                // ...
                .signingAlgorithms { sign: MutableList<String?> ->
                    sign.add(
                        SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
                    )
                }
        }
        .build()
上面的代码片段使用了 OpenSAML 的 SignatureConstants 类来提供算法名称。 但这只是为了方便起见。 由于该数据类型是 String,你可以直接提供算法的名称。

某些断言方要求通过 POST 方式发送 <saml2:AuthnRequest>。 这可以通过 RelyingPartyRegistrations 自动配置,也可以手动提供,如下所示:spring-doc.cadn.net.cn

RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyMetadata((party) -> party
            // ...
            .singleSignOnServiceBinding(Saml2MessageBinding.POST)
        )
        .build();
var relyingPartyRegistration: RelyingPartyRegistration? =
    RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
            // ...
            .singleSignOnServiceBinding(Saml2MessageBinding.POST)
        }
        .build()

自定义 OpenSAML 的AuthnRequest实例

你可能有多种原因需要调整一个 AuthnRequest。 例如,你可能希望将 ForceAuthN 设置为 true,而 Spring Security 默认将其设为 falsespring-doc.cadn.net.cn

你可以通过将一个 AuthnRequest 发布为 OpenSaml5AuthenticationRequestResolver 来自定义 OpenSAML 的 @Bean 元素,如下所示:spring-doc.cadn.net.cn

@Bean
Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
    RelyingPartyRegistrationResolver registrationResolver =
            new DefaultRelyingPartyRegistrationResolver(registrations);
    OpenSaml5AuthenticationRequestResolver authenticationRequestResolver =
            new OpenSaml5AuthenticationRequestResolver(registrationResolver);
    authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
            .getAuthnRequest().setForceAuthn(true));
    return authenticationRequestResolver;
}
@Bean
fun authenticationRequestResolver(registrations : RelyingPartyRegistrationRepository) : Saml2AuthenticationRequestResolver {
    val registrationResolver : RelyingPartyRegistrationResolver =
            new DefaultRelyingPartyRegistrationResolver(registrations)
    val authenticationRequestResolver : OpenSaml5AuthenticationRequestResolver =
            new OpenSaml5AuthenticationRequestResolver(registrationResolver)
    authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
            .getAuthnRequest().setForceAuthn(true))
    return authenticationRequestResolver
}