SAML 2.0 登入概觀
我們先從檢視 SAML 2.0 Relying Party 身份驗證如何在 Spring Security 內運作開始。首先,我們看到,如同 OAuth 2.0 登入,Spring Security 會將使用者導向至第三方以執行身份驗證。它透過一系列的重新導向來達成此目的。

上圖是建立在我們的 |
首先,使用者對
/private
資源發出未經身份驗證的請求,但使用者未被授權存取該資源。
Spring Security 的
AuthorizationFilter
表示未經身份驗證的請求被拒絕,並拋出 AccessDeniedException
。
由於使用者缺乏授權,
ExceptionTranslationFilter
啟動開始身份驗證。已組態的 AuthenticationEntryPoint
是 LoginUrlAuthenticationEntryPoint
的實例,它會重新導向至 產生 <saml2:AuthnRequest>
端點,Saml2WebSsoAuthenticationRequestFilter
。或者,如果您已組態多個宣告方,它會先重新導向至選擇器頁面。
接著,
Saml2WebSsoAuthenticationRequestFilter
使用其已組態的 Saml2AuthenticationRequestFactory
建立、簽署、序列化和編碼 <saml2:AuthnRequest>
。
然後,瀏覽器取得此
<saml2:AuthnRequest>
並將其呈現給宣告方。宣告方嘗試驗證使用者身份。如果成功,它會將 <saml2:Response>
回應給瀏覽器。
瀏覽器接著將
<saml2:Response>
POST 到斷言消費者服務端點。
下圖顯示 Spring Security 如何驗證 <saml2:Response>
。

<saml2:Response>
此圖是建立在我們的 |
當瀏覽器將
<saml2:Response>
提交到應用程式時,它會委派給 Saml2WebSsoAuthenticationFilter
。此篩選器呼叫其已組態的 AuthenticationConverter
,以透過從 HttpServletRequest
提取回應來建立 Saml2AuthenticationToken
。此轉換器還會解析 RelyingPartyRegistration
並將其提供給 Saml2AuthenticationToken
。
接著,篩選器將權杖傳遞給其已組態的
AuthenticationManager
。預設情況下,它使用 OpenSamlAuthenticationProvider
。
如果身份驗證失敗,則為失敗。
-
會調用
AuthenticationEntryPoint
以重新啟動身份驗證程序。
如果身份驗證成功,則為成功。
-
會在
SecurityContextHolder
上設定Authentication
。 -
Saml2WebSsoAuthenticationFilter
調用FilterChain#doFilter(request,response)
以繼續執行應用程式邏輯的其餘部分。
最低相依性
SAML 2.0 服務提供者支援位於 spring-security-saml2-service-provider
中。它建立在 OpenSAML 程式庫之上,因此,您還必須在組建組態中包含 Shibboleth Maven 儲存庫。請查看此連結,以瞭解為何需要單獨的儲存庫的更多詳細資訊。
-
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'
}
最低組態
當使用 Spring Boot 時,將應用程式組態為服務提供者包含兩個基本步驟:. 包含所需的相依性。 . 指示必要的宣告方 Metadata。
此外,此組態以前提是您已向宣告方註冊了 Relying Party。 |
指定身份提供者 Metadata
在 Spring Boot 應用程式中,若要指定身份提供者的 Metadata,請建立類似於以下的組態
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
其中
-
idp.example.com/issuer
是身份提供者發出的 SAML 回應的Issuer
屬性中包含的值。 -
classpath:idp.crt
是 classpath 上身份提供者的憑證位置,用於驗證 SAML 回應。 -
idp.example.com/issuer/sso
是身份提供者預期接收AuthnRequest
實例的端點。 -
adfs
是您選擇的任意識別碼
就是這樣!
身份提供者和宣告方是同義詞,服務提供者和 Relying Party 也是同義詞。這些經常縮寫為 AP 和 RP。 |
執行階段預期
如先前組態的,應用程式會處理任何包含 SAMLResponse
參數的 POST /login/saml2/sso/{registrationId}
請求。
POST /login/saml2/sso/adfs HTTP/1.1
SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...
有兩種方法可以誘導宣告方產生 SAMLResponse
。
-
您可以導航至宣告方。它可能具有某種連結或按鈕,適用於您可以點擊的每個已註冊的 Relying Party,以傳送
SAMLResponse
。 -
您可以導航至應用程式中的受保護頁面 — 例如,
localhost:8080
。您的應用程式接著會重新導向至已組態的宣告方,然後宣告方會傳送SAMLResponse
。
從這裡開始,考慮跳轉到
SAML 2.0 登入如何與 OpenSAML 整合
Spring Security 的 SAML 2.0 支援有幾個設計目標。
-
依賴程式庫進行 SAML 2.0 操作和網域物件。為了實現此目的,Spring Security 使用 OpenSAML。
-
確保在使用 Spring Security 的 SAML 支援時,不需要此程式庫。為了實現此目的,Spring Security 在合約中使用 OpenSAML 的任何介面或類別都保持封裝。這使您可以切換出 OpenSAML 以使用其他程式庫或不受支援版本的 OpenSAML。
作為這兩個目標的自然結果,相對於其他模組,Spring Security 的 SAML API 非常小。相反,諸如 OpenSamlAuthenticationRequestFactory
和 OpenSamlAuthenticationProvider
之類的類別公開了 Converter
實作,這些實作自訂了身份驗證程序中的各個步驟。
例如,一旦您的應用程式收到 SAMLResponse
並委派給 Saml2WebSsoAuthenticationFilter
,篩選器就會委派給 OpenSamlAuthenticationProvider
。
Response
此圖建立在 Saml2WebSsoAuthenticationFilter
圖表之上。
Saml2WebSsoAuthenticationFilter
公式化 Saml2AuthenticationToken
並調用 AuthenticationManager
。
AuthenticationManager
調用 OpenSAML 身份驗證提供者。
身份驗證提供者將回應反序列化為 OpenSAML
Response
並檢查其簽章。如果簽章無效,則身份驗證失敗。
然後,提供者解密任何
EncryptedAssertion
元素。如果任何解密失敗,則身份驗證失敗。
接著,提供者驗證回應的
Issuer
和 Destination
值。如果它們與 RelyingPartyRegistration
中的內容不符,則身份驗證失敗。
之後,提供者驗證每個
Assertion
的簽章。如果任何簽章無效,則身份驗證失敗。此外,如果回應和斷言都沒有簽章,則身份驗證失敗。回應或所有斷言都必須具有簽章。
然後,提供者,解密任何
EncryptedID
或 EncryptedAttribute
元素]。如果任何解密失敗,則身份驗證失敗。
接著,提供者驗證每個斷言的
ExpiresAt
和 NotBefore
時間戳記、<Subject>
和任何 <AudienceRestriction>
條件。如果任何驗證失敗,則身份驗證失敗。
接下來,提供者取得第一個斷言的
AttributeStatement
並將其映射到 Map<String, List<Object>>
。它還授予 ROLE_USER
授權。
最後,它從第一個斷言中取得
NameID
、屬性的 Map
和 GrantedAuthority
並建構 Saml2AuthenticatedPrincipal
。然後,它將該主體和授權放入 Saml2Authentication
。
產生的 Authentication#getPrincipal
是 Spring Security Saml2AuthenticatedPrincipal
物件,而 Authentication#getName
映射到第一個斷言的 NameID
元素。Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId
保留與關聯的 RelyingPartyRegistration
的識別碼。
自訂 OpenSAML 組態
任何同時使用 Spring Security 和 OpenSAML 的類別都應在類別的開頭靜態初始化 OpenSamlInitializationService
。
-
Java
-
Kotlin
static {
OpenSamlInitializationService.initialize();
}
companion object {
init {
OpenSamlInitializationService.initialize()
}
}
這會取代 OpenSAML 的 InitializationService#initialize
。
有時,自訂 OpenSAML 如何建置、封送處理和解封送處理 SAML 物件可能很有價值。在這些情況下,您可能反而想要呼叫 OpenSamlInitializationService#requireInitialize(Consumer)
,這讓您可以存取 OpenSAML 的 XMLObjectProviderFactory
。
例如,當傳送未簽署的 AuthNRequest 時,您可能想要強制重新驗證。在這種情況下,您可以註冊自己的 AuthnRequestMarshaller
,如下所示
-
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
方法每個應用程式實例只能調用一次。
覆寫或取代 Boot 自動組態
Spring Boot 為 Relying Party 產生兩個 @Bean
物件。
第一個是 SecurityFilterChain
,它將應用程式組態為 Relying Party。當包含 spring-security-saml2-service-provider
時,SecurityFilterChain
看起來像這樣
-
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。
您可以透過在應用程式中公開 bean 來取代它。
-
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
角色。
Spring Boot 建立的第二個 @Bean
是一個 RelyingPartyRegistrationRepository
,它代表宣告方和 Relying Party Metadata。這包括諸如 Relying Party 從宣告方請求身份驗證時應使用的 SSO 端點位置之類的事項。
您可以透過發佈自己的 RelyingPartyRegistrationRepository
bean 來覆寫預設值。例如,您可以透過點擊宣告方的 Metadata 端點來查找其組態。
-
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)
}
registrationId 是您選擇用於區分註冊的任意值。 |
或者,您可以手動提供每個詳細資訊。
-
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 直接連接儲存庫,這也會覆寫自動組態的 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()
}
}
Relying Party 可以是多租戶的,方法是在 |
RelyingPartyRegistration
RelyingPartyRegistration
實例代表 Relying Party 和宣告方的 Metadata 之間的連結。
在 RelyingPartyRegistration
中,您可以提供 Relying Party Metadata,例如其 Issuer
值、它預期 SAML 回應傳送到的位置,以及它為了簽署或解密酬載而擁有的任何憑證。
此外,您可以提供宣告方 Metadata,例如其 Issuer
值、它預期 AuthnRequest 傳送到的位置,以及它為了 Relying Party 驗證或加密酬載而擁有的任何公開憑證。
以下 RelyingPartyRegistration
是大多數設定所需的最低限度。
-
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
。其中一個範例是 Metadata 儲存在資料庫中時。
String xml = fromDatabase();
try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadata(source)
.registrationId("my-id")
.build();
}
更複雜的設定也是可能的。
-
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()
最上層的 Metadata 方法是有關 Relying Party 的詳細資訊。 |
Relying Party 預期 SAML 回應的位置是斷言消費者服務位置。 |
Relying Party 的 entityId
的預設值為 {baseUrl}/saml2/service-provider-metadata/{registrationId}
。這是組態宣告方以瞭解您的 Relying Party 時所需的值。
assertionConsumerServiceLocation
的預設值為 /login/saml2/sso/{registrationId}
。預設情況下,它會映射到篩選器鏈中的 Saml2WebSsoAuthenticationFilter
。
URI 模式
您可能已注意到先前範例中的 {baseUrl}
和 {registrationId}
佔位符。
這些對於產生 URI 非常有用。因此,Relying Party 的 entityId
和 assertionConsumerServiceLocation
支援以下佔位符。
-
baseUrl
- 已部署應用程式的 scheme、host 和 port -
registrationId
- 此 Relying Party 的註冊 ID -
baseScheme
- 已部署應用程式的 scheme -
baseHost
- 已部署應用程式的 host -
basePort
- 已部署應用程式的 port
例如,先前定義的 assertionConsumerServiceLocation
是
/my-login-endpoint/{registrationId}
在已部署的應用程式中,它會轉換為
/my-login-endpoint/adfs
先前顯示的 entityId
定義為
{baseUrl}/{registrationId}
在已部署的應用程式中,它會轉換為
https://rp.example.com/adfs
主要的 URI 模式如下。
-
/saml2/authenticate/{registrationId}
- 產生<saml2:AuthnRequest>
的端點,該請求基於該RelyingPartyRegistration
的組態並將其傳送至宣告方。 -
/login/saml2/sso/
- 驗證宣告方的<saml2:Response>
的端點;RelyingPartyRegistration
會從先前驗證的狀態或回應的發行者 (如果需要) 查找;也支援/login/saml2/sso/{registrationId}
。 -
/logout/saml2/sso
- 處理<saml2:LogoutRequest>
和<saml2:LogoutResponse>
酬載的端點;RelyingPartyRegistration
會從先前驗證的狀態或請求的發行者 (如果需要) 查找;也支援/logout/saml2/slo/{registrationId}
。 -
/saml2/metadata
- Relying Party Metadata,適用於RelyingPartyRegistration
的集合;也支援針對特定RelyingPartyRegistration
的/saml2/metadata/{registrationId}
或/saml2/service-provider-metadata/{registrationId}
。
由於 registrationId
是 RelyingPartyRegistration
的主要識別碼,因此在未驗證的情況下,URL 中需要它。如果您希望基於任何原因從 URL 中移除 registrationId
,您可以指定 RelyingPartyRegistrationResolver
,以告知 Spring Security 如何查找 registrationId
。
憑證
在先前顯示的範例中,您可能也注意到使用的憑證。
通常,Relying Party 使用相同的金鑰來簽署酬載以及解密它們。或者,它可以使用相同的金鑰來驗證酬載以及加密它們。
因此,Spring Security 隨附 Saml2X509Credential
,這是一種 SAML 專用憑證,可簡化為不同使用案例組態相同金鑰的過程。
至少,您需要擁有來自宣告方的憑證,以便可以驗證宣告方已簽署的回應。
若要建構可用於驗證來自宣告方的斷言的 Saml2X509Credential
,您可以載入檔案並使用 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?
)
}
假設宣告方也將加密斷言。在這種情況下,Relying Party 需要私密金鑰來解密加密的值。
在這種情況下,您需要一個 RSAPrivateKey
及其對應的 X509Certificate
。您可以使用 Spring Security 的 RsaKeyConverters
實用程式類別載入第一個,並像之前一樣載入第二個。
-
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 會為您執行這些轉換。 |
重複的 Relying Party 組態
當應用程式使用多個宣告方時,某些組態會在 RelyingPartyRegistration
實例之間重複。
-
Relying Party 的
entityId
-
其
assertionConsumerServiceLocation
-
其憑證 — 例如,其簽署或解密憑證
此設定可能會讓某些身份提供者的憑證比其他身份提供者更容易輪換。
可以透過幾種不同的方式減輕重複。
首先,在 YAML 中,可以使用參考來減輕這種情況。
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
的模型。
第三,在 Java 中,您可以建立自訂組態方法。
-
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)
}
從請求解析 RelyingPartyRegistration
到目前為止,Spring Security 透過在 URI 路徑中查找註冊 ID 來解析 RelyingPartyRegistration
。
根據使用案例,也採用了許多其他策略來衍生一個。例如
-
對於處理
<saml2:Response>
,RelyingPartyRegistration
是從關聯的<saml2:AuthRequest>
或從<saml2:Response#Issuer>
元素中查找的。 -
對於處理
<saml2:LogoutRequest>
,RelyingPartyRegistration
是從目前登入的使用者或從<saml2:LogoutRequest#Issuer>
元素中查找的。 -
對於發佈 Metadata,
RelyingPartyRegistration
是從也實作Iterable<RelyingPartyRegistration>
的任何儲存庫中查找的。
當需要調整時,您可以求助於針對自訂此項目的這些端點中的每一個的特定元件。
-
對於 SAML 回應,自訂
AuthenticationConverter
。 -
對於登出請求,自訂
Saml2LogoutRequestValidatorParametersResolver
。 -
對於 Metadata,自訂
Saml2MetadataResponseResolver
。
聯合登入
SAML 2.0 的一種常見安排是具有多個宣告方的身份提供者。在這種情況下,身份提供者的 Metadata 端點會傳回多個 <md:IDPSSODescriptor>
元素。
可以在對 RelyingPartyRegistrations
的單次呼叫中存取這些多個宣告方,如下所示。
-
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 端點變為不可預測。有多種方法可以解決此問題;讓我們專注於一種適合聯合特定使用案例的方法。
在許多聯合案例中,所有宣告方都共用服務提供者組態。鑑於 Spring Security 預設會將 registrationId
包含在服務提供者 Metadata 中,因此另一個步驟是變更對應的 URI 以排除 registrationId
,您可以看到在上述範例中已完成此步驟,其中 entityId
和 assertionConsumerServiceLocation
組態為靜態端點。
您可以在 我們的 saml-extension-federation
範例中看到已完成的範例。
使用 Spring Security SAML 擴充功能 URI
如果您要從 Spring Security SAML 擴充功能遷移,則組態您的應用程式以使用 SAML 擴充功能 URI 預設值可能會有一些好處。
如需更多相關資訊,請參閱我們的 custom-urls
範例和我們的 saml-extension-federation
範例。