生成 <saml2:AuthnRequest>

如前所述,Spring Security 的 SAML 2.0 支持会生成一个 <saml2:AuthnRequest> 以便与声明方开始身份验证。

Spring Security 部分通过在过滤器链中注册 Saml2WebSsoAuthenticationRequestFilter 来实现这一点。此过滤器默认响应端点 /saml2/authenticate/{registrationId}/saml2/authenticate?registrationId={registrationId}

例如,如果您部署到 rp.example.com,并且您将您的注册 ID 设置为 okta,您可以导航到

结果将是一个重定向,其中包含一个 SAMLRequest 参数,该参数包含已签名、解压缩和编码的 <saml2:AuthnRequest>

配置 <saml2:AuthnRequest> 端点

要将端点配置为与默认值不同,您可以在 saml2Login 中设置值

  • Java

  • Kotlin

@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> 发送 给声明方。

此外,Saml2WebSsoAuthenticationFilterSaml2AuthenticationTokenConverter 使用 Saml2AuthenticationRequestRepository 来加载任何 AbstractSaml2AuthenticationRequest 作为 验证 <saml2:Response> 的一部分。

默认情况下,Spring Security 使用 HttpSessionSaml2AuthenticationRequestRepository,它将 AbstractSaml2AuthenticationRequest 存储在 HttpSession 中。

如果您有 Saml2AuthenticationRequestRepository 的自定义实现,您可以通过将其公开为 @Bean 来配置它,如以下示例所示

  • Java

  • Kotlin

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

按中继状态缓存 <saml2:AuthnRequest>

如果您不想使用会话来存储 <saml2:AuthnRequest>,您也可以将其存储在分布式缓存中。如果您尝试使用 SameSite=Strict 并且在从身份提供商重定向时丢失了身份验证请求,这会很有帮助。

重要的是要记住,将它存储在会话中有安全优势。其中一个好处是它提供了自然的登录固定防御。例如,如果应用程序从会话中查找身份验证请求,那么即使攻击者向受害者提供了他们自己的 SAML 响应,登录也会失败。

另一方面,如果我们信任 InResponseTo 或 RelayState 来检索身份验证请求,那么就无法知道 SAML 响应是否由该握手请求。

为了解决这个问题,Spring Security 提供了 CacheSaml2AuthenticationRequestRepository,您可以将其作为 bean 发布,供过滤器链使用

  • Java

  • Kotlin

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

更改 <saml2:AuthnRequest> 的发送方式

默认情况下,Spring Security 会签署每个 <saml2:AuthnRequest> 并将其作为 GET 请求发送给声明方。

许多声明方不需要已签名的 <saml2:AuthnRequest>。这可以通过 RelyingPartyRegistrations 自动配置,或者您可以手动提供,如下所示

不需要已签名的身份验证请求
  • 引导

  • Java

  • Kotlin

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 Security 将使用 rsa-sha256 签署 <saml2:AuthnRequest>,尽管某些声明方将需要不同的算法,如其元数据中所示。

您可以根据声明方的 元数据使用 RelyingPartyRegistrations 配置算法。

或者,您可以手动提供它

  • Java

  • Kotlin

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,您可以直接提供算法的名称。

一些声明方要求 <saml2:AuthnRequest> 以 POST 方式发送。这可以通过 RelyingPartyRegistrations 自动配置,或者您可以手动提供,如下所示

  • Java

  • Kotlin

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

您可以通过将 OpenSaml5AuthenticationRequestResolver 发布为 @Bean 来自定义 OpenSAML 的 AuthnRequest 元素,如下所示

  • Java

  • Kotlin

@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
}
© . This site is unofficial and not affiliated with VMware.