生成 <saml2:AuthnRequest>
<saml2:AuthnRequest>s
如前所述,Spring Security 的 SAML 2.0 支持会生成一个 <saml2:AuthnRequest> 来启动与断言方的认证流程。
Spring Security 部分通过向过滤器链注册 Saml2WebSsoAuthenticationRequestFilter 来实现此功能。该过滤器默认响应端点 /saml2/authenticate/{registrationId} 和 /saml2/authenticate?registrationId={registrationId}。
例如,如果您部署到 [rp.example.com](https://rp.example.com) 并为您的注册分配了 ID okta,您可以导航到:
[rp.example.org/saml2/authenticate/okta](https://rp.example.org/saml2/authenticate/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 在发送 <saml2:AuthnRequest> 到断言方之前,使用一个 Saml2AuthenticationRequestRepository 来持久化一个 AbstractSaml2AuthenticationRequest 实例。
此外,Saml2WebSsoAuthenticationFilter 和 Saml2AuthenticationTokenConverter 使用 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 自动配置,也可以手动提供,如下所示:
- Boot
- 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,你也可以直接提供算法名称。
某些断言方要求通过POST方式提交 <saml2:AuthnRequest>。这可以通过 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
}