此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3spring-doc.cadn.net.cn

生产<saml2:AuthnRequest>s

如前所述,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并且您为注册提供了 IDokta,您可以导航到:spring-doc.cadn.net.cn

结果将是一个重定向,其中包含一个SAMLRequest包含 signed、deflated 和 encoded 的参数<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,它存储AbstractSaml2AuthenticationRequestHttpSession.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>由中继州

如果您不想使用会话来存储<saml2:AuthnRequest>,您也可以将其存储在分布式缓存中。 如果您尝试使用SameSite=Strict并且在来自身份提供者的重定向中丢失了身份验证请求。spring-doc.cadn.net.cn

请务必记住,将其存储在会话中具有安全优势。 其中一个好处是它提供的自然登录固定防御。 例如,如果应用程序从会话中查找身份验证请求,那么即使攻击者向受害者提供自己的 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

不需要签名的 AuthnRequests
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

或者,您可以手动提供它: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()
上面的代码段使用 OpenSAMLSignatureConstantsclass 来提供算法名称。 但是,这只是为了方便。 由于数据类型是String,您可以直接提供算法的名称。

一些断言方要求<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 设置为false默认情况下。spring-doc.cadn.net.cn

您可以自定义 OpenSAML 的AuthnRequest通过发布OpenSaml5AuthenticationRequestResolver作为@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
}