SAML 2.0 Login Overview
我们首先检查 SAML 2.0 依靠方身份验证如何在 Spring Security 中起作用。首先,我们看到,像 RFC 1901 一样,Spring Security 会将用户带到第三方执行身份验证。它通过一系列重定向来执行此操作:
We start by examining how SAML 2.0 Relying Party Authentication works within Spring Security. First, we see that, like oauth2login, Spring Security takes the user to a third party for performing authentication. It does this through a series of redirects: .Redirecting to Asserting Party Authentication image::servlet/saml2/saml2webssoauthenticationrequestfilter.png[]
上图建立在我们的 The figure above builds off our |
首先,用户向 /private
资源发送未经验证的请求,并且对该资源没有获得授权。
First, a user makes an unauthenticated request to the /private
resource, for which it is not authorized.
Spring Security 的 AuthorizationFilter
通过抛出 AccessDeniedException
表明未经验证的请求是 Denied。
Spring Security’s AuthorizationFilter
indicates that the unauthenticated request is Denied by throwing an AccessDeniedException
.
由于用户缺乏授权,ExceptionTranslationFilter
启动 Start Authentication。配置的 AuthenticationEntryPoint
是 {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[LoginUrlAuthenticationEntryPoint
] 的一个实例,它重定向到 the <saml2:AuthnRequest>
generating endpoint,Saml2WebSsoAuthenticationRequestFilter
。或者,如果您有 configured more than one asserting party,则它首先重定向到选择器页面。
Since the user lacks authorization, the ExceptionTranslationFilter
initiates Start Authentication.
The configured AuthenticationEntryPoint
is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[LoginUrlAuthenticationEntryPoint
], which redirects to servlet-saml2login-sp-initiated-factory, Saml2WebSsoAuthenticationRequestFilter
.
Alternatively, if you have servlet-saml2login-relyingpartyregistrationrepository, it first redirects to a picker page.
接下来,Saml2WebSsoAuthenticationRequestFilter`使用其配置的 <<`Saml2AuthenticationRequestFactory
,servlet-saml2login-sp-initiated-factory>> 创建、签名、序列化和编码 <saml2:AuthnRequest>
。
Next, the Saml2WebSsoAuthenticationRequestFilter
creates, signs, serializes, and encodes a <saml2:AuthnRequest>
using its configured <<`Saml2AuthenticationRequestFactory`,servlet-saml2login-sp-initiated-factory>>.
然后浏览器将此 `<saml2:AuthnRequest>`带给验证方。验证方尝试验证用户。如果成功,则将 `<saml2:Response>`返回给浏览器。
Then the browser takes this <saml2:AuthnRequest>
and presents it to the asserting party.
The asserting party tries to authentication the user.
If successful, it returns a <saml2:Response>
back to the browser.
然后浏览器将 <saml2:Response>
POST 到声明使用者服务端点。
The browser then POSTs the <saml2:Response>
to the assertion consumer service endpoint.
下图显示了 Spring Security 如何对 RFC 1901 进行身份验证。
The following image shows how Spring Security authenticates a <saml2:Response>
.
<saml2:Response>
此图建立在我们的 The figure builds off our |
当浏览器向应用程序提交 <saml2:Response>`时,它会 delegates to `Saml2WebSsoAuthenticationFilter
。此筛选器调用其配置的 AuthenticationConverter`以通过从 `HttpServletRequest`中提取响应来创建 `Saml2AuthenticationToken
。此转换器另外解析 <<`RelyingPartyRegistration`,servlet-saml2login-relyingpartyregistration>> 并将其提供给 Saml2AuthenticationToken
。
When the browser submits a <saml2:Response>
to the application, it delegates to Saml2WebSsoAuthenticationFilter
.
This filter calls its configured AuthenticationConverter
to create a Saml2AuthenticationToken
by extracting the response from the HttpServletRequest
.
This converter additionally resolves the <<`RelyingPartyRegistration`,servlet-saml2login-relyingpartyregistration>> and supplies it to Saml2AuthenticationToken
.
接下来,筛选器将令牌传递给其配置的 AuthenticationManager
。默认情况下,它使用 <<`OpenSamlAuthenticationProvider`,servlet-saml2login-architecture>>。
Next, the filter passes the token to its configured AuthenticationManager
.
By default, it uses the <<`OpenSamlAuthenticationProvider`,servlet-saml2login-architecture>>.
如果验证失败,则 Failure。
If authentication fails, then Failure.
-
The
SecurityContextHolder
is cleared out. -
The
AuthenticationEntryPoint
is invoked to restart the authentication process.
如果验证成功,则 Success。
If authentication is successful, then Success.
-
The
Authentication
is set on theSecurityContextHolder
. -
The
Saml2WebSsoAuthenticationFilter
invokesFilterChain#doFilter(request,response)
to continue with the rest of the application logic.
Minimal Dependencies
SAML 2.0 服务提供程序支持驻留在 `spring-security-saml2-service-provider`中。它建立在 OpenSAML 库之上,因此,您还必须在构建配置中包含 Shibboleth Maven 存储库。查看 this link以了解有关为何需要单独的存储库的更多详细信息。
SAML 2.0 service provider support resides in spring-security-saml2-service-provider
.
It builds off of the OpenSAML library, and, for that reason, you must also include the Shibboleth Maven repository in your build configuration.
Check this link for more details about why a separate repository is needed.
-
Maven
-
Gradle
<repositories>
<!-- ... -->
<repository>
<id>shibboleth-releases</id>
<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
</repository>
</repositories>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-saml2-service-provider</artifactId>
</dependency>
repositories {
// ...
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
dependencies {
// ...
implementation 'org.springframework.security:spring-security-saml2-service-provider'
}
Minimal Configuration
在使用 Spring Boot时,将应用程序配置为服务提供程序包括两个基本步骤:包含所需的依赖项。指示必要的验证方元数据。
When using Spring Boot, configuring an application as a service provider consists of two basic steps: . Include the needed dependencies. . Indicate the necessary asserting party metadata.
Also, this configuration presupposes that you have already registered the relying party with your asserting party. |
Specifying Identity Provider Metadata
在 Spring Boot 应用程序中,要指定身份提供程序的元数据,请创建类似于以下内容的配置:
In a Spring Boot application, to specify an identity provider’s metadata, create configuration similar to the following:
spring:
security:
saml2:
relyingparty:
registration:
adfs:
identityprovider:
entity-id: https://idp.example.com/issuer
verification.credentials:
- certificate-location: "classpath:idp.crt"
singlesignon.url: https://idp.example.com/issuer/sso
singlesignon.sign-request: false
其中:
where:
-
https://idp.example.com/issuer
is the value contained in theIssuer
attribute of the SAML responses that the identity provider issues. -
classpath:idp.crt
is the location on the classpath for the identity provider’s certificate for verifying SAML responses. -
https://idp.example.com/issuer/sso
is the endpoint where the identity provider is expectingAuthnRequest
instances.
这就是全部!
And that’s it!
身份提供程序和主张方是同义词,服务提供程序和依赖方也是同义词。这些通常分别缩写为 AP 和 RP。 Identity Provider and Asserting Party are synonymous, as are Service Provider and Relying Party. These are frequently abbreviated as AP and RP, respectively. |
Runtime Expectations
在 earlier 中进行配置中,应用程序处理包含 SAMLResponse
参数的任何 POST /login/saml2/sso/{registrationId}
请求:
As configured saml2-specifying-identity-provider-metadata, the application processes any POST /login/saml2/sso/{registrationId}
request containing a SAMLResponse
parameter:
POST /login/saml2/sso/adfs HTTP/1.1
SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...
有两种方法可以诱导主张方生成 SAMLResponse
:
There are two ways to induce your asserting party to generate a SAMLResponse
:
-
You can navigate to your asserting party. It likely has some kind of link or button for each registered relying party that you can click to send the
SAMLResponse
. -
You can navigate to a protected page in your application — for example,
http://localhost:8080
. Your application then redirects to the configured asserting party, which then sends theSAMLResponse
.
从此处,考虑跳转到:
From here, consider jumping to:
How SAML 2.0 Login Integrates with OpenSAML
Spring Security 的 SAML 2.0 支持有几个设计目标:
Spring Security’s SAML 2.0 support has a couple of design goals:
-
Rely on a library for SAML 2.0 operations and domain objects. To achieve this, Spring Security uses OpenSAML.
-
Ensure that this library is not required when using Spring Security’s SAML support. To achieve this, any interfaces or classes where Spring Security uses OpenSAML in the contract remain encapsulated. This makes it possible for you to switch out OpenSAML for some other library or an unsupported version of OpenSAML.
作为这两个目标的自然结果,与其他模块相比,Spring Security 的 SAML API 非常小。相反,OpenSamlAuthenticationRequestFactory
和 OpenSamlAuthenticationProvider
这样的类会公开 Converter
实现,这些实现可以自定义身份验证过程中的各个步骤。
As a natural outcome of these two goals, Spring Security’s SAML API is quite small relative to other modules.
Instead, such classes as OpenSamlAuthenticationRequestFactory
and OpenSamlAuthenticationProvider
expose Converter
implementations that customize various steps in the authentication process.
例如,一旦应用程序收到 SAMLResponse
并委托给 Saml2WebSsoAuthenticationFilter
,过滤器将委托给 OpenSamlAuthenticationProvider
:
For example, once your application receives a SAMLResponse
and delegates to Saml2WebSsoAuthenticationFilter
, the filter delegates to OpenSamlAuthenticationProvider
:
Response
此图表基于 <<`Saml2WebSsoAuthenticationFilter` 图表,servlet-saml2login-authentication-saml2webssoauthenticationfilter>>。
This figure builds off of the <<`Saml2WebSsoAuthenticationFilter` diagram,servlet-saml2login-authentication-saml2webssoauthenticationfilter>>.
Saml2WebSsoAuthenticationFilter`制定 `Saml2AuthenticationToken`并调用 `AuthenticationManager
。
The Saml2WebSsoAuthenticationFilter
formulates the Saml2AuthenticationToken
and invokes the AuthenticationManager
.
AuthenticationManager
调用 OpenSAML 身份验证提供程序。
The AuthenticationManager
invokes the OpenSAML authentication provider.
验证提供程序将响应反序列化为 OpenSAML `Response`并检查其签名。如果签名无效,则验证失败。
The authentication provider deserializes the response into an OpenSAML Response
and checks its signature.
If the signature is invalid, authentication fails.
然后提供程序 decrypts any EncryptedAssertion
elements。如果任何解密失败,身份验证失败。
Then the provider decrypts any EncryptedAssertion
elements.
If any decryptions fail, authentication fails.
接下来,提供者验证响应的 `Issuer`和 `Destination`值。如果它们与 `RelyingPartyRegistration`中的值不匹配,则验证失败。
Next, the provider validates the response’s Issuer
and Destination
values.
If they do not match what’s in the RelyingPartyRegistration
, authentication fails.
之后,提供者验证每个 `Assertion`的签名。如果任何签名无效,则验证失败。此外,如果响应或断言都没有签名,则验证失败。响应或所有断言都必须有签名。
After that, the provider verifies the signature of each Assertion
.
If any signature is invalid, authentication fails.
Also, if neither the response nor the assertions have signatures, authentication fails.
Either the response or all the assertions must have signatures.
然后,提供程序 ,解密任何 `EncryptedID`或 `EncryptedAttribute`元素]。如果任何解密失败,身份验证失败。
Then, the provider ,decrypts any EncryptedID
or EncryptedAttribute
elements].
If any decryptions fail, authentication fails.
接下来,提供者验证每个断言的 ExpiresAt`和 `NotBefore`时间戳、
<Subject>`和任何 `<AudienceRestriction>`条件。如果任何验证失败,则验证失败。
Next, the provider validates each assertion’s ExpiresAt
and NotBefore
timestamps, the <Subject>
and any <AudienceRestriction>
conditions.
If any validations fail, authentication fails.
在此之后,提供者获取第一个断言的 AttributeStatement`并将其映射到 `Map<String, List<Object>>
。它还授予 `ROLE_USER`授予的权限。
Following that, the provider takes the first assertion’s AttributeStatement
and maps it to a Map<String, List<Object>>
.
It also grants the ROLE_USER
granted authority.
最后,它从第一个断言中获取 NameID
,从属性中获取 Map
,从 GrantedAuthority`构建了 `Saml2AuthenticatedPrincipal
。然后,它将该主体和权限放入 `Saml2Authentication`中。
And finally, it takes the NameID
from the first assertion, the Map
of attributes, and the GrantedAuthority
and constructs a Saml2AuthenticatedPrincipal
.
Then, it places that principal and the authorities into a Saml2Authentication
.
最终的 Authentication#getPrincipal
是一个 Spring Security Saml2AuthenticatedPrincipal
对象,而 Authentication#getName
映射到第一个断言的 NameID
元素。Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId
保留 identifier to the associated RelyingPartyRegistration
。
The resulting Authentication#getPrincipal
is a Spring Security Saml2AuthenticatedPrincipal
object, and Authentication#getName
maps to the first assertion’s NameID
element.
Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId
holds the servlet-saml2login-relyingpartyregistrationid.
Customizing OpenSAML Configuration
同时使用 Spring Security 和 OpenSAML 的任何类都应在类的开头静态初始化 OpenSamlInitializationService
:
Any class that uses both Spring Security and OpenSAML should statically initialize OpenSamlInitializationService
at the beginning of the class:
-
Java
-
Kotlin
static {
OpenSamlInitializationService.initialize();
}
companion object {
init {
OpenSamlInitializationService.initialize()
}
}
这会替换 OpenSAML 的 InitializationService#initialize
。
This replaces OpenSAML’s InitializationService#initialize
.
有时候,自定义 OpenSAML 构建、编组和反编组 SAML 对象的方式是很有价值的。在这种情况下,您可能希望调用 OpenSamlInitializationService#requireInitialize(Consumer)
,这会让您访问 OpenSAML 的 XMLObjectProviderFactory
。
Occasionally, it can be valuable to customize how OpenSAML builds, marshalls, and unmarshalls SAML objects.
In these circumstances, you may instead want to call OpenSamlInitializationService#requireInitialize(Consumer)
that gives you access to OpenSAML’s XMLObjectProviderFactory
.
例如,在发送无符号 AuthNRequest 时,您可能希望强制重新认证。在该情况下,您可以注册您自己的 AuthnRequestMarshaller
,如下所示:
For example, when sending an unsigned AuthNRequest, you may want to force reauthentication.
In that case, you can register your own AuthnRequestMarshaller
, like so:
-
Java
-
Kotlin
static {
OpenSamlInitializationService.requireInitialize(factory -> {
AuthnRequestMarshaller marshaller = new AuthnRequestMarshaller() {
@Override
public Element marshall(XMLObject object, Element element) throws MarshallingException {
configureAuthnRequest((AuthnRequest) object);
return super.marshall(object, element);
}
public Element marshall(XMLObject object, Document document) throws MarshallingException {
configureAuthnRequest((AuthnRequest) object);
return super.marshall(object, document);
}
private void configureAuthnRequest(AuthnRequest authnRequest) {
authnRequest.setForceAuthn(true);
}
}
factory.getMarshallerFactory().registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller);
});
}
companion object {
init {
OpenSamlInitializationService.requireInitialize {
val marshaller = object : AuthnRequestMarshaller() {
override fun marshall(xmlObject: XMLObject, element: Element): Element {
configureAuthnRequest(xmlObject as AuthnRequest)
return super.marshall(xmlObject, element)
}
override fun marshall(xmlObject: XMLObject, document: Document): Element {
configureAuthnRequest(xmlObject as AuthnRequest)
return super.marshall(xmlObject, document)
}
private fun configureAuthnRequest(authnRequest: AuthnRequest) {
authnRequest.isForceAuthn = true
}
}
it.marshallerFactory.registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller)
}
}
}
requireInitialize
方法可以每个应用程序实例只调用一次。
The requireInitialize
method may be called only once per application instance.
Overriding or Replacing Boot Auto Configuration
Spring Boot 会为依赖方生成两个 @Bean
对象。
Spring Boot generates two @Bean
objects for a relying party.
第一个是 SecurityFilterChain
,它把应用程序配置为依赖方。在包括 spring-security-saml2-service-provider
时,SecurityFilterChain
如下所示:
The first is a SecurityFilterChain
that configures the application as a relying party.
When including spring-security-saml2-service-provider
, the SecurityFilterChain
looks like:
-
Java
-
Kotlin
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.saml2Login(withDefaults());
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login { }
}
return http.build()
}
如果应用程序没有公开 SecurityFilterChain
bean,Spring Boot 会公开前面的默认 bean。
If the application does not expose a SecurityFilterChain
bean, Spring Boot exposes the preceding default one.
您可以通过在应用程序中公开 bean 来替换这个:
You can replace this by exposing the bean within the application:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/messages/**").hasAuthority("ROLE_USER")
.anyRequest().authenticated()
)
.saml2Login(withDefaults());
return http.build();
}
}
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/messages/**", hasAuthority("ROLE_USER"))
authorize(anyRequest, authenticated)
}
saml2Login {
}
}
return http.build()
}
}
前面的示例要求任何以 /messages/
开头的 URL 都有 USER
的角色。
The preceding example requires the role of USER
for any URL that starts with /messages/
.
Spring Boot 创建的第二个 @Bean`为 {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[`RelyingPartyRegistrationRepository
],它表示断言方和验证方元数据。这包括内容,例如,验证方在从断言方请求身份验证时应使用的 SSO 端点的位置。
The second @Bean
Spring Boot creates is a {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[RelyingPartyRegistrationRepository
], which represents the asserting party and relying party metadata.
This includes such things as the location of the SSO endpoint the relying party should use when requesting authentication from the asserting party.
您可以通过发布您自己的 RelyingPartyRegistrationRepository
bean 来覆盖默认。例如,您可以通过访问断言方的元数据端点来查看其配置:
You can override the default by publishing your own RelyingPartyRegistrationRepository
bean.
For example, you can look up the asserting party’s configuration by hitting its metadata endpoint:
-
Java
-
Kotlin
@Value("${metadata.location}")
String assertingPartyMetadataLocation;
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId("example")
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
@Value("\${metadata.location}")
var assertingPartyMetadataLocation: String? = null
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
val registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId("example")
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
|
The |
或者,您可以手动提供每个详细信息:
Alternatively, you can provide each detail manually:
-
Java
-
Kotlin
@Value("${verification.key}")
File verificationKey;
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
X509Certificate certificate = X509Support.decodeCertificate(this.verificationKey);
Saml2X509Credential credential = Saml2X509Credential.verification(certificate);
RelyingPartyRegistration registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyDetails(party -> party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
.wantAuthnRequestsSigned(false)
.verificationX509Credentials(c -> c.add(credential))
)
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
@Value("\${verification.key}")
var verificationKey: File? = null
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository {
val certificate: X509Certificate? = X509Support.decodeCertificate(verificationKey!!)
val credential: Saml2X509Credential = Saml2X509Credential.verification(certificate)
val registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyDetails { party: AssertingPartyDetails.Builder ->
party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
.wantAuthnRequestsSigned(false)
.verificationX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(
credential
)
}
}
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
|
或者,您可以使用 DSL 直接连接存储库,该 DSL 也会覆盖自动配置的 SecurityFilterChain
:
Alternatively, you can directly wire up the repository by using the DSL, which also overrides the auto-configured SecurityFilterChain
:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/messages/**").hasAuthority("ROLE_USER")
.anyRequest().authenticated()
)
.saml2Login(saml2 -> saml2
.relyingPartyRegistrationRepository(relyingPartyRegistrations())
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/messages/**", hasAuthority("ROLE_USER"))
authorize(anyRequest, authenticated)
}
saml2Login {
relyingPartyRegistrationRepository = relyingPartyRegistrations()
}
}
return http.build()
}
}
依赖方可以通过在 A relying party can be multi-tenant by registering more than one relying party in the |
RelyingPartyRegistration
{security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[RelyingPartyRegistration
] 实例表示验证方和断言方元数据之间的链接。
A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[RelyingPartyRegistration
]
instance represents a link between an relying party and an asserting party’s metadata.
在 RelyingPartyRegistration
中,您可以提供诸如 Issuer
值等依赖方元数据,它期望 SAML 响应发送给它,和它为签名或解密有效负载所拥有的所有证书。
In a RelyingPartyRegistration
, you can provide relying party metadata like its Issuer
value, where it expects SAML Responses to be sent to, and any credentials that it owns for the purposes of signing or decrypting payloads.
此外,您可以提供断言方元数据,如其`Issuer`值、它预期 AuthnRequests 被发送到的位置,以及它拥有的任何公共凭据,以供依赖方验证或加密有效负载。
Also, you can provide asserting party metadata like its Issuer
value, where it expects AuthnRequests to be sent to, and any public credentials that it owns for the purposes of the relying party verifying or encrypting payloads.
以下 RelyingPartyRegistration
是大多数设置的最低要求:
The following RelyingPartyRegistration
is the minimum required for most setups:
-
Java
-
Kotlin
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("my-id")
.build();
val relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("my-id")
.build()
注意,您还可以从任意 InputStream
源创建 RelyingPartyRegistration
。一个这样的示例是当元数据存储在数据库中时:
Note that you can also create a RelyingPartyRegistration
from an arbitrary InputStream
source.
One such example is when the metadata is stored in a database:
String xml = fromDatabase();
try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadata(source)
.registrationId("my-id")
.build();
}
还可以进行更复杂的设置:
A more sophisticated setup is also possible:
-
Java
-
Kotlin
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
.entityId("{baseUrl}/{registrationId}")
.decryptionX509Credentials(c -> c.add(relyingPartyDecryptingCredential()))
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyDetails(party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials(c -> c.add(assertingPartyVerifyingCredential()))
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
)
.build();
val relyingPartyRegistration =
RelyingPartyRegistration.withRegistrationId("my-id")
.entityId("{baseUrl}/{registrationId}")
.decryptionX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(relyingPartyDecryptingCredential())
}
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyDetails { party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials { c -> c.add(assertingPartyVerifyingCredential()) }
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
}
.build()
顶级元数据方法是关于依赖方的详细信息。 The top-level metadata methods are details about the relying party.
The methods inside |
依赖方预期 SAML 响应的位置是断言使用者服务位置。 The location where a relying party is expecting SAML Responses is the Assertion Consumer Service Location. |
依赖方的 entityId
的默认值是 {baseUrl}/saml2/service-provider-metadata/{registrationId}
。这是在配置断言方以了解您的依赖方所需的值。
The default for the relying party’s entityId
is {baseUrl}/saml2/service-provider-metadata/{registrationId}
.
This is this value needed when configuring the asserting party to know about your relying party.
assertionConsumerServiceLocation
的默认值为 /login/saml2/sso/{registrationId}
。默认情况下,它映射到过滤器链中的 <<`Saml2WebSsoAuthenticationFilter`,servlet-saml2login-authentication-saml2webssoauthenticationfilter>>。
The default for the assertionConsumerServiceLocation
is /login/saml2/sso/{registrationId}
.
By default, it is mapped to <<`Saml2WebSsoAuthenticationFilter`,servlet-saml2login-authentication-saml2webssoauthenticationfilter>> in the filter chain.
URI Patterns
您可能注意到了前面示例中的 {baseUrl}
和 {registrationId}
占位符。
You probably noticed the {baseUrl}
and {registrationId}
placeholders in the preceding examples.
这些对于生成 URI 很有用。因此,依赖方的 entityId
和 assertionConsumerServiceLocation
支持以下占位符:
These are useful for generating URIs. As a result, the relying party’s entityId
and assertionConsumerServiceLocation
support the following placeholders:
-
baseUrl
- the scheme, host, and port of a deployed application -
registrationId
- the registration id for this relying party -
baseScheme
- the scheme of a deployed application -
baseHost
- the host of a deployed application -
basePort
- the port of a deployed application
例如,前面定义的 assertionConsumerServiceLocation
为:
For example, the assertionConsumerServiceLocation
defined earlier was:
/my-login-endpoint/{registrationId}
在已部署的应用程序中,它转换为:
In a deployed application, it translates to:
/my-login-endpoint/adfs
前面显示的 entityId
被定义为:
The entityId
shown earlier was defined as:
{baseUrl}/{registrationId}
在已部署的应用程序中,这转换为:
In a deployed application, that translates to:
当前的 URI 模式如下:
The prevailing URI patterns are as follows:
-
/saml2/authenticate/{registrationId}
- The endpoint that generates a<saml2:AuthnRequest>
based on the configurations for thatRelyingPartyRegistration
and sends it to the asserting party -
/login/saml2/sso/
- The endpoint that authenticates an asserting party’s<saml2:Response>
; theRelyingPartyRegistration
is looked up from previously authenticated state or the response’s issuer if needed; also supports/login/saml2/sso/{registrationId}
-
/logout/saml2/sso
- The endpoint that processes<saml2:LogoutRequest>
and<saml2:LogoutResponse>
payloads; theRelyingPartyRegistration
is looked up from previously authenticated state or the request’s issuer if needed; also supports/logout/saml2/slo/{registrationId}
-
/saml2/metadata
- The relying party metadata for the set ofRelyingPartyRegistration`s; also supports `/saml2/metadata/{registrationId}
or/saml2/service-provider-metadata/{registrationId}
for a specificRelyingPartyRegistration
由于 registrationId
是 RelyingPartyRegistration
的主要标识符,因此在未经身份验证的情况下需要在 URL 中使用它。如果您出于任何原因希望从 URL 中删除 registrationId
,您可以 specify a RelyingPartyRegistrationResolver
告诉 Spring Security 如何查找 registrationId
。
Since the registrationId
is the primary identifier for a RelyingPartyRegistration
, it is needed in the URL for unauthenticated scenarios.
If you wish to remove the registrationId
from the URL for any reason, you can servlet-saml2login-rpr-relyingpartyregistrationresolver to tell Spring Security how to look up the registrationId
.
Credentials
在earlier所显示的示例中,您还可能注意到使用凭证。
In the example shown servlet-saml2login-relyingpartyregistration, you also likely noticed the credential that was used.
通常情况下,依赖方使用相同的密钥来签署有效负载以及解密它们。或者,它可以使用相同的密钥来验证有效负载以及加密它们。
Oftentimes, a relying party uses the same key to sign payloads as well as decrypt them. Alternatively, it can use the same key to verify payloads as well as encrypt them.
因此,Spring Security随附`Saml2X509Credential`,这是一个专门用于 SAML 的凭证,它简化了在不同使用案例中配置相同密钥的操作。
Because of this, Spring Security ships with Saml2X509Credential
, a SAML-specific credential that simplifies configuring the same key for different use cases.
至少,您需要来自断言方的证书,以便可以验证断言方的签名响应。
At a minimum, you need to have a certificate from the asserting party so that the asserting party’s signed responses can be verified.
要构建用于验证来自断言方的断言的`Saml2X509Credential`,可以加载文件并使用 CertificateFactory
:
To construct a Saml2X509Credential
that you can use to verify assertions from the asserting party, you can load the file and use
the CertificateFactory
:
-
Java
-
Kotlin
Resource resource = new ClassPathResource("ap.crt");
try (InputStream is = resource.getInputStream()) {
X509Certificate certificate = (X509Certificate)
CertificateFactory.getInstance("X.509").generateCertificate(is);
return Saml2X509Credential.verification(certificate);
}
val resource = ClassPathResource("ap.crt")
resource.inputStream.use {
return Saml2X509Credential.verification(
CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate?
)
}
假设断言方也将加密断言。在这种情况下,依赖方需要私钥来解密加密值。
Suppose that the asserting party is going to also encrypt the assertion. In that case, the relying party needs a private key to decrypt the encrypted value.
在这种情况下,您需要 RSAPrivateKey
及其对应的 X509Certificate
。可以使用 Spring Security 的 RsaKeyConverters
实用程序类加载第一个,并按照之前的做法加载第二个:
In that case, you need an RSAPrivateKey
as well as its corresponding X509Certificate
.
You can load the first by using Spring Security’s RsaKeyConverters
utility class and the second as you did before:
-
Java
-
Kotlin
X509Certificate certificate = relyingPartyDecryptionCertificate();
Resource resource = new ClassPathResource("rp.crt");
try (InputStream is = resource.getInputStream()) {
RSAPrivateKey rsa = RsaKeyConverters.pkcs8().convert(is);
return Saml2X509Credential.decryption(rsa, certificate);
}
val certificate: X509Certificate = relyingPartyDecryptionCertificate()
val resource = ClassPathResource("rp.crt")
resource.inputStream.use {
val rsa: RSAPrivateKey = RsaKeyConverters.pkcs8().convert(it)
return Saml2X509Credential.decryption(rsa, certificate)
}
当您将这些文件的路径指定为相应的 Spring Boot 属性时,Spring Boot 会为您执行这些转换。 When you specify the locations of these files as the appropriate Spring Boot properties, Spring Boot performs these conversions for you. |
Duplicated Relying Party Configurations
当应用程序使用多个断言方时,某些配置会在 RelyingPartyRegistration
实例之间重复:
When an application uses multiple asserting parties, some configuration is duplicated between RelyingPartyRegistration
instances:
-
The relying party’s
entityId
-
Its
assertionConsumerServiceLocation
-
Its credentials — for example, its signing or decryption credentials
对于某些标识提供程序与其他标识提供程序相比,这种设置可以更容易地轮换凭证。
This setup may let credentials be more easily rotated for some identity providers versus others.
可以通过一些不同的方式来缓解重复。
The duplication can be alleviated in a few different ways.
首先,在 YAML 中,可以使用引用来缓解这种情况:
First, in YAML this can be alleviated with references:
spring:
security:
saml2:
relyingparty:
okta:
signing.credentials: &relying-party-credentials
- private-key-location: classpath:rp.key
certificate-location: classpath:rp.crt
identityprovider:
entity-id: ...
azure:
signing.credentials: *relying-party-credentials
identityprovider:
entity-id: ...
其次,在数据库中,您无需复制 RelyingPartyRegistration
的模型。
Second, in a database, you need not replicate the model of RelyingPartyRegistration
.
第三,在 Java 中,您可以创建自定义配置方法:
Third, in Java, you can create a custom configuration method:
-
Java
-
Kotlin
private RelyingPartyRegistration.Builder
addRelyingPartyDetails(RelyingPartyRegistration.Builder builder) {
Saml2X509Credential signingCredential = ...
builder.signingX509Credentials(c -> c.addAll(signingCredential));
// ... other relying party configurations
}
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
RelyingPartyRegistration okta = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("okta")).build();
RelyingPartyRegistration azure = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("azure")).build();
return new InMemoryRelyingPartyRegistrationRepository(okta, azure);
}
private fun addRelyingPartyDetails(builder: RelyingPartyRegistration.Builder): RelyingPartyRegistration.Builder {
val signingCredential: Saml2X509Credential = ...
builder.signingX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(
signingCredential
)
}
// ... other relying party configurations
}
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
val okta = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("okta")
).build()
val azure = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("azure")
).build()
return InMemoryRelyingPartyRegistrationRepository(okta, azure)
}
Resolving the RelyingPartyRegistration
from the Request
如前所述,Spring Security 通过在 URI 路径中查找注册 ID 来解决 RelyingPartyRegistration
。
As seen so far, Spring Security resolves the RelyingPartyRegistration
by looking for the registration id in the URI path.
根据使用案例,也采用许多其他策略来推导策略。例如:
Depending on the use case, a number of other strategies are also employed to derive one. For example:
-
For processing
<saml2:Response>`s, the `RelyingPartyRegistration
is looked up from the associated<saml2:AuthRequest>
or from the<saml2:Response#Issuer>
element -
For processing
<saml2:LogoutRequest>`s, the `RelyingPartyRegistration
is looked up from the currently logged in user or from the<saml2:LogoutRequest#Issuer>
element -
For publishing metadata, the
RelyingPartyRegistration`s are looked up from any repository that also implements `Iterable<RelyingPartyRegistration>
当需要进行调整时,您可以对针对自定义此功能的每个端点的特定组件进行调整:
When this needs adjustment, you can turn to the specific components for each of these endpoints targeted at customizing this:
-
For SAML Responses, customize the
AuthenticationConverter
-
For Logout Requests, customize the
Saml2LogoutRequestValidatorParametersResolver
-
For Metadata, customize the
Saml2MetadataResponseResolver
Federating Login
SAML 2.0 的一种常见设置是具有多个断言方的标识提供程序。在这种情况下,标识提供程序的元数据端点返回多个 <md:IDPSSODescriptor>
元素。
One common arrangement with SAML 2.0 is an identity provider that has multiple asserting parties.
In this case, the identity provider’s metadata endpoint returns multiple <md:IDPSSODescriptor>
elements.
可以使用以下方式以单个调用 RelyingPartyRegistrations
访问这些多个断言方:
These multiple asserting parties can be accessed in a single call to RelyingPartyRegistrations
like so:
-
Java
-
Kotlin
Collection<RelyingPartyRegistration> registrations = RelyingPartyRegistrations
.collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml")
.stream().map((builder) -> builder
.registrationId(UUID.randomUUID().toString())
.entityId("https://example.org/saml2/sp")
.build()
)
.collect(Collectors.toList()));
var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrations
.collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml")
.stream().map { builder : RelyingPartyRegistration.Builder -> builder
.registrationId(UUID.randomUUID().toString())
.entityId("https://example.org/saml2/sp")
.assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso")
.build()
}
.collect(Collectors.toList()));
请注意,由于注册 ID 被设置为随机值,因此这将导致某些 SAML 2.0 端点变得不可预测。有几种方法可以解决此问题;我们重点关注一种适合联合特定使用案例的方法。
Note that because the registration id is set to a random value, this will change certain SAML 2.0 endpoints to be unpredictable. There are several ways to address this; let’s focus on a way that suits the specific use case of federation.
在很多联合体案例中,所有断言方都共享服务提供商配置。由于 Spring Security 将默认在服务提供商元数据中包含 registrationId
,另一步骤是更改对应的 URI 以排除 registrationId
,您可以在上面样例中看到已完成该步骤,其中 entityId
和 assertionConsumerServiceLocation
配置有静态端点。
In many federation cases, all the asserting parties share service provider configuration.
Given that Spring Security will by default include the registrationId
in the service provider metadata, another step is to change corresponding URIs to exclude the registrationId
, which you can see has already been done in the above sample where the entityId
and assertionConsumerServiceLocation
are configured with a static endpoint.
您可以在 我们的 `saml-extension-federation`示例 中看到它的一个完整示例。
You can see a completed example of this in our saml-extension-federation
sample.
Using Spring Security SAML Extension URIs
如果您正在从 Spring Security SAML Extension 迁移,可能通过将应用程序配置为使用 SAML Extension URI 默认值受益。
In the event that you are migrating from the Spring Security SAML Extension, there may be some benefit to configuring your application to use the SAML Extension URI defaults.
有关此内容的更多信息,请参阅 我们的 `custom-urls`示例 和 我们的 `saml-extension-federation`示例。
For more information on this, please see our custom-urls
sample and our saml-extension-federation
sample.