|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4! |
生成 <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> 给断言方。
此外,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>通过 Relay State
如果你不想使用会话(session)来存储 <saml2:AuthnRequest>,也可以将其存储在分布式缓存中。
当你尝试使用 SameSite=Strict 时,这会很有帮助,因为在这种情况下,从身份提供商(Identity Provider)重定向回来时可能会丢失认证请求。
|
记住将它存储在会话(session)中具有安全优势这一点非常重要。 其中一个优势是它能自然地防御登录固定(login fixation)攻击。 例如,如果应用程序从会话中查找身份验证请求,那么即使攻击者向受害者提供他们自己的 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 会使用 <saml2:AuthnRequest> 对 rsa-sha256 进行签名,但某些身份提供方可能会在其元数据中指定要求使用不同的算法。
您可以基于主张方的元数据使用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。
你可以通过将一个 AuthnRequest 发布为 OpenSaml5AuthenticationRequestResolver 来自定义 OpenSAML 的 @Bean 元素,如下所示:
-
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
}