此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cadn.net.cn

SAML 2.0 登录概述

我们首先研究 SAML 2.0 依赖方身份验证在 Spring Security 中的工作原理。 首先,我们看到,与 OAuth 2.0 登录一样,Spring Security 将用户带到第三方进行身份验证。 它通过一系列重定向来实现这一点:spring-doc.cadn.net.cn

saml2webssoauthenticationrequestfilter
图 1.重定向至“断言 Party Authentication”诊断树

数字 1首先,用户向/private资源,但未获得授权。spring-doc.cadn.net.cn

编号 2Spring Security 的AuthorizationFilter表示未经身份验证的请求被拒绝,方法是抛出AccessDeniedException.spring-doc.cadn.net.cn

编号 3由于用户没有授权,因此ExceptionTranslationFilter启动 Start Authentication。 配置的AuthenticationEntryPointLoginUrlAuthenticationEntryPoint,它会重定向到<saml2:AuthnRequest>生成终端节点,Saml2WebSsoAuthenticationRequestFilter. 或者,如果您配置了多个断言方,则它首先重定向到选取器页面。spring-doc.cadn.net.cn

编号 4接下来,Saml2WebSsoAuthenticationRequestFilter创建、签署、序列化和编码<saml2:AuthnRequest>使用其配置的Saml2AuthenticationRequestFactory.spring-doc.cadn.net.cn

号码 5然后浏览器会采用这个<saml2:AuthnRequest>并将其提交给主张方。 断言方尝试对用户进行身份验证。 如果成功,它将返回一个<saml2:Response>返回浏览器。spring-doc.cadn.net.cn

数字 6然后,浏览器将<saml2:Response>添加到断言使用者服务终端节点。spring-doc.cadn.net.cn

下图显示了 Spring Security 如何对<saml2:Response>.spring-doc.cadn.net.cn

saml2webssoauthenticationfilter
图 2.验证<saml2:Response>

该图建立在我们的SecurityFilterChain图。spring-doc.cadn.net.cn

数字 1当浏览器提交<saml2:Response>对于应用程序,它delegates 到Saml2WebSsoAuthenticationFilter. 此过滤器将其配置的AuthenticationConverter要创建一个Saml2AuthenticationToken通过从HttpServletRequest. 此转换器还解决了RelyingPartyRegistration并将其提供给Saml2AuthenticationToken.spring-doc.cadn.net.cn

编号 2接下来,过滤器将令牌传递给其配置的AuthenticationManager. 默认情况下,它使用OpenSamlAuthenticationProvider.spring-doc.cadn.net.cn

编号 3如果身份验证失败,则为 Failurespring-doc.cadn.net.cn

编号 4如果身份验证成功,则为 Successspring-doc.cadn.net.cn

最小依赖项

SAML 2.0 服务提供商支持位于spring-security-saml2-service-provider. 它基于 OpenSAML 库构建,因此,您还必须在构建配置中包含 Shibboleth Maven 存储库。 查看此链接,了解有关为什么需要单独存储库的更多详细信息。spring-doc.cadn.net.cn

<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 时,将应用程序配置为服务提供商包括两个基本步骤: .包括所需的依赖项。 .指示必要的断言方元数据。spring-doc.cadn.net.cn

此外,此配置还假定您已向断言方注册了依赖方

指定身份提供程序元数据

在 Spring Boot 应用程序中,要指定身份提供者的元数据,请创建类似于以下内容的配置:spring-doc.cadn.net.cn

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

就是这样!spring-doc.cadn.net.cn

身份提供商和断言方是同义词,服务提供商和依赖方也是同义词。 它们通常分别缩写为 AP 和 RP。spring-doc.cadn.net.cn

运行时预期

如前所述,应用程序会处理任何POST /login/saml2/sso/{registrationId}请求中包含SAMLResponse参数:spring-doc.cadn.net.cn

POST /login/saml2/sso/adfs HTTP/1.1

SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...

有两种方法可以诱使您的断言方生成SAMLResponse:spring-doc.cadn.net.cn

  • 您可以导航到您的断言方。 它可能为每个已注册的依赖方提供某种链接或按钮,您可以单击这些链接或按钮以将SAMLResponse.spring-doc.cadn.net.cn

  • 您可以导航到应用程序中的受保护页面,例如localhost:8080. 然后,您的应用程序重定向到配置的断言方,然后该方将SAMLResponse.spring-doc.cadn.net.cn

从这里,考虑跳到:spring-doc.cadn.net.cn

SAML 2.0 登录如何与 OpenSAML 集成

Spring Security 的 SAML 2.0 支持有几个设计目标:spring-doc.cadn.net.cn

  • 依赖 SAML 2.0作和域对象的库。 为了实现这一点,Spring Security 使用 OpenSAML。spring-doc.cadn.net.cn

  • 确保在使用 Spring Security 的 SAML 支持时不需要此库。 为了实现这一点,Spring Security 在 Contract 中使用 OpenSAML 的任何接口或类都保持封装状态。 这样,您就可以将 OpenSAML 切换为其他库或不受支持的 OpenSAML 版本。spring-doc.cadn.net.cn

作为这两个目标的自然结果, Spring Security 的 SAML API 相对于其他模块来说非常小。 相反,诸如OpenSamlAuthenticationRequestFactoryOpenSamlAuthenticationProvider暴露Converter自定义身份验证过程中各个步骤的实现。spring-doc.cadn.net.cn

例如,一旦您的应用程序收到SAMLResponse和委托人Saml2WebSsoAuthenticationFilter,筛选器将委托给OpenSamlAuthenticationProvider:spring-doc.cadn.net.cn

对 OpenSAML 进行身份验证Response

OpenSAML AuthenticationProviderspring-doc.cadn.net.cn

数字 1Saml2WebSsoAuthenticationFilter制定Saml2AuthenticationToken并调用AuthenticationManager.spring-doc.cadn.net.cn

编号 2AuthenticationManager调用 OpenSAML 身份验证提供程序。spring-doc.cadn.net.cn

编号 3身份验证提供程序将响应反序列化为 OpenSAMLResponse并检查其签名。 如果签名无效,则鉴权失败。spring-doc.cadn.net.cn

编号 4然后是提供商解密任何EncryptedAssertion元素. 如果任何解密失败,则身份验证将失败。spring-doc.cadn.net.cn

号码 5接下来,提供程序验证响应的IssuerDestination值。 如果它们与RelyingPartyRegistration,则身份验证失败。spring-doc.cadn.net.cn

数字 6之后,提供程序会验证每个Assertion. 如果签名无效,则鉴权失败。 此外,如果响应和断言都没有签名,则身份验证将失败。 响应或所有断言都必须具有签名。spring-doc.cadn.net.cn

编号 7然后,提供程序 解密任何EncryptedIDEncryptedAttribute元素]。 如果任何解密失败,则身份验证将失败。spring-doc.cadn.net.cn

编号 8接下来,提供程序验证每个断言的ExpiresAtNotBeforetimestamps、<Subject>和任何<AudienceRestriction>条件。 如果任何验证失败,则身份验证失败。spring-doc.cadn.net.cn

编号 9然后,提供程序采用第一个断言的AttributeStatement并将其映射到Map<String, List<Object>>. 它还授予ROLE_USER授予权限。spring-doc.cadn.net.cn

编号 10最后,它需要NameID从第一个断言开始,Mapof 属性和GrantedAuthority并构造一个Saml2AuthenticatedPrincipal. 然后,它将该 principal 和权限放入Saml2Authentication.spring-doc.cadn.net.cn

结果Authentication#getPrincipal是 Spring SecuritySaml2AuthenticatedPrincipalobject 和Authentication#getName映射到第一个断言的NameID元素。Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId持有identifier 添加到关联的RelyingPartyRegistration.spring-doc.cadn.net.cn

自定义 OpenSAML 配置

任何同时使用 Spring Security 和 OpenSAML 的类都应该静态初始化OpenSamlInitializationService在课程开始时:spring-doc.cadn.net.cn

static {
	OpenSamlInitializationService.initialize();
}
companion object {
    init {
        OpenSamlInitializationService.initialize()
    }
}

这取代了 OpenSAML 的InitializationService#initialize.spring-doc.cadn.net.cn

有时,自定义 OpenSAML 构建、封送和取消封送 SAML 对象的方式可能很有价值。 在这些情况下,您可能希望调用OpenSamlInitializationService#requireInitialize(Consumer)这使您能够访问 OpenSAML 的XMLObjectProviderFactory.spring-doc.cadn.net.cn

例如,在发送未签名的 AuthNRequest 时,您可能希望强制重新进行身份验证。 在这种情况下,您可以注册自己的AuthnRequestMarshaller这样:spring-doc.cadn.net.cn

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)
        }
    }
}

requireInitializemethod 每个应用程序实例只能调用一次。spring-doc.cadn.net.cn

覆盖或替换引导自动配置

Spring Boot 生成两个@Bean对象。spring-doc.cadn.net.cn

第一个是SecurityFilterChain,将应用程序配置为依赖方。 当包含spring-security-saml2-service-providerSecurityFilterChain看来:spring-doc.cadn.net.cn

默认 SAML 2.0 登录配置
@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()
}

如果应用程序未公开SecurityFilterChainbean,则 Spring Boot 会公开前面的默认 Bean。spring-doc.cadn.net.cn

您可以通过在应用程序中公开 bean 来替换它:spring-doc.cadn.net.cn

自定义 SAML 2.0 登录配置
@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()
    }
}

前面的示例需要USER对于任何以/messages/.spring-doc.cadn.net.cn

第二个@BeanSpring Boot 创建的是一个RelyingPartyRegistrationRepository,它表示断言方和依赖方元数据。 这包括依赖方在向断言方请求身份验证时应使用的 SSO 端点的位置等内容。spring-doc.cadn.net.cn

您可以通过发布自己的RelyingPartyRegistrationRepository豆。 例如,您可以通过点击其元数据端点来查找断言方的配置:spring-doc.cadn.net.cn

依赖方注册存储库
@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是您选择用于区分注册的任意值。

或者,您可以手动提供每个详细信息:spring-doc.cadn.net.cn

依赖方注册存储库手动配置
@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)
}

X509Support是一个 OpenSAML 类,为简洁起见,在前面的代码段中使用。spring-doc.cadn.net.cn

或者,您可以使用 DSL 直接连接存储库,这也会覆盖自动配置的SecurityFilterChain:spring-doc.cadn.net.cn

自定义依赖方注册 DSL
@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()
    }
}

通过在RelyingPartyRegistrationRepository.spring-doc.cadn.net.cn

RelyingParty注册

一个RelyingPartyRegistrationinstance 表示依赖方和断言方的元数据之间的链接。spring-doc.cadn.net.cn

RelyingPartyRegistration,您可以提供依赖方元数据,例如其Issuer值,它希望将 SAML 响应发送到其中,以及它拥有的用于对负载进行签名或解密的任何凭证。spring-doc.cadn.net.cn

此外,您还可以提供断言参与方元数据,例如其Issuer值,它希望将 AuthnRequests 发送到其中,以及它拥有的任何公有凭证,以便依赖方验证或加密负载。spring-doc.cadn.net.cn

以下内容RelyingPartyRegistration是大多数设置所需的最低要求:spring-doc.cadn.net.cn

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()

请注意,您还可以创建一个RelyingPartyRegistration从任意InputStream源。 一个这样的例子是当元数据存储在数据库中时:spring-doc.cadn.net.cn

String xml = fromDatabase();
try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
    RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
            .fromMetadata(source)
            .registrationId("my-id")
            .build();
}

还可以进行更复杂的设置:spring-doc.cadn.net.cn

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()

顶级元数据方法是有关信赖方的详细信息。 里面的方法assertingPartyDetails是有关断言方的详细信息。spring-doc.cadn.net.cn

依赖方需要 SAML 响应的位置是断言使用者服务位置。spring-doc.cadn.net.cn

依赖方的entityId{baseUrl}/saml2/service-provider-metadata/{registrationId}. 这是配置断言方以了解依赖方时所需的此值。spring-doc.cadn.net.cn

默认的assertionConsumerServiceLocation/login/saml2/sso/{registrationId}. 默认情况下,它被映射到Saml2WebSsoAuthenticationFilter在过滤器链中。spring-doc.cadn.net.cn

URI 模式

您可能已经注意到了{baseUrl}{registrationId}placeholders 的 Brackets 进行引用。spring-doc.cadn.net.cn

这些对于生成 URI 非常有用。因此,依赖方的entityIdassertionConsumerServiceLocation支持以下占位符:spring-doc.cadn.net.cn

例如,assertionConsumerServiceLocation之前定义的是:spring-doc.cadn.net.cn

/my-login-endpoint/{registrationId}spring-doc.cadn.net.cn

在已部署的应用程序中,它转换为:spring-doc.cadn.net.cn

/my-login-endpoint/adfsspring-doc.cadn.net.cn

entityId前面显示的定义是:spring-doc.cadn.net.cn

{baseUrl}/{registrationId}spring-doc.cadn.net.cn

在已部署的应用程序中,这转换为:spring-doc.cadn.net.cn

https://rp.example.com/adfsspring-doc.cadn.net.cn

流行的 URI 模式如下:spring-doc.cadn.net.cn

由于registrationIdRelyingPartyRegistration,则对于未经身份验证的方案,URL 中需要它。 如果要删除registrationId无论出于何种原因,您都可以指定一个RelyingPartyRegistrationResolver告诉 Spring Security 如何查找registrationId.spring-doc.cadn.net.cn

凭据

前面显示的示例中,您可能还注意到了所使用的凭证。spring-doc.cadn.net.cn

通常,依赖方使用相同的密钥对有效负载进行签名和解密。 或者,它可以使用相同的密钥来验证有效负载并对其进行加密。spring-doc.cadn.net.cn

因此,Spring Security 附带了Saml2X509Credential,一种特定于 SAML 的凭证,可简化为不同使用案例配置相同密钥的过程。spring-doc.cadn.net.cn

您至少需要拥有来自断言方的证书,以便可以验证断言方的签名响应。spring-doc.cadn.net.cn

要构造Saml2X509Credential,您可以使用它来验证来自断言方的断言,您可以加载该文件并使用 这CertificateFactory:spring-doc.cadn.net.cn

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?
    )
}

假设断言方也要加密断言。 在这种情况下,信赖方需要私钥来解密加密的值。spring-doc.cadn.net.cn

在这种情况下,您需要一个RSAPrivateKey及其相应的X509Certificate. 您可以使用 Spring Security 的RsaKeyConvertersUtility 类和第二个类一样:spring-doc.cadn.net.cn

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 会为您执行这些转换。spring-doc.cadn.net.cn

重复的依赖方配置

当应用程序使用多个断言方时,某些配置在RelyingPartyRegistration实例:spring-doc.cadn.net.cn

与其他身份提供商相比,此设置可以更轻松地为某些身份提供商轮换凭据。spring-doc.cadn.net.cn

可以通过几种不同的方式缓解重复。spring-doc.cadn.net.cn

首先,在 YAML 中,这可以通过引用来缓解:spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

第三,在 Java 中,您可以创建自定义配置方法:spring-doc.cadn.net.cn

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 解析了RelyingPartyRegistration通过在 URI 路径中查找注册 ID。spring-doc.cadn.net.cn

根据用例,还采用了许多其他策略来派生一个。 例如:spring-doc.cadn.net.cn

  • 用于加工<saml2:Response>`s, the `RelyingPartyRegistration从关联的<saml2:AuthRequest>或从<saml2:Response#Issuer>元素spring-doc.cadn.net.cn

  • 用于加工<saml2:LogoutRequest>`s, the `RelyingPartyRegistration从当前登录的用户或<saml2:LogoutRequest#Issuer>元素spring-doc.cadn.net.cn

  • 对于发布元数据,RelyingPartyRegistration`s are looked up from any repository that also implements `Iterable<RelyingPartyRegistration>spring-doc.cadn.net.cn

当这需要调整时,您可以转向每个端点的特定组件,以自定义此端点:spring-doc.cadn.net.cn

联合登录

SAML 2.0 的一种常见安排是具有多个断言方的身份提供商。 在这种情况下,身份提供商的元数据终端节点将返回多个<md:IDPSSODescriptor>元素。spring-doc.cadn.net.cn

这些多个断言方可以通过对RelyingPartyRegistrations这样:spring-doc.cadn.net.cn

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-doc.cadn.net.cn

在许多联合身份验证情况下,所有断言方共享服务提供商配置。 鉴于 Spring Security 默认包含registrationId在服务提供商元数据中,另一个步骤是更改相应的 URI 以排除registrationId,您可以看到,在上面的示例中已经完成了此作,其中entityIdassertionConsumerServiceLocation配置了静态终端节点。spring-doc.cadn.net.cn

使用 Spring Security SAML 扩展 URI

如果要从 Spring Security SAML 扩展迁移,则将应用程序配置为使用 SAML 扩展 URI 默认值可能会有一些好处。spring-doc.cadn.net.cn