(window.webpackJsonp=window.webpackJsonp||[]).push([[648],{1080:function(e,t,i){"use strict";i.r(t);var a=i(56),n=Object(a.a)({},(function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[i("h1",{attrs:{id:"saml2-0登录概述"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#saml2-0登录概述"}},[e._v("#")]),e._v(" SAML2.0登录概述")]),e._v(" "),i("p",[e._v("让我们来看看SAML2.0依赖方身份验证在 Spring 安全性中是如何工作的。首先,我们看到,与"),i("RouterLink",{attrs:{to:"/oauth2/login/index.html"}},[e._v("OAuth2.0登录")]),e._v("类似, Spring 安全性将用户带到第三方进行身份验证。它通过一系列重定向来实现这一点。")],1),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/servlet/saml2/saml2webssoauthenticationrequestfilter.png",alt:"SAML2WebssoAuthenticationRequestFilter"}})]),e._v(" "),i("p",[e._v("图1.重定向到断言的一方身份验证")]),e._v(" "),i("p",[e._v("上图构建了我们的["),i("code",[e._v("SecurityFilterChain")]),e._v("](../../architecture.html# Servlet-securityfilterchain)和["),i("code",[e._v("AbstractAuthenticationProcessingFilter")]),e._v("](.../../authentication/architecture.html# Servlet-authentication-abstractprocessingfilter)图:")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_1.png",alt:"number 1"}}),e._v("首先,用户向资源"),i("code",[e._v("/private")]),e._v("发出未经授权的请求。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_2.png",alt:"number 2"}}),e._v(" Spring security的["),i("code",[e._v("FilterSecurityInterceptor")]),e._v("](.../授权/authorization/authorization/authorization-requests.html# Servlet-authorization-filtersecurityinterceptor)通过抛出"),i("code",[e._v("AccessDeniedException")]),e._v("表示未经验证的请求"),i("em",[e._v("拒绝")]),e._v("。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_3.png",alt:"number 3"}}),e._v("由于用户缺乏授权,["),i("code",[e._v("ExceptionTranslationFilter")]),e._v("](../../architecture.html# Servlet-ExceptionTranslationFilter)发起"),i("em",[e._v("启动身份验证")]),e._v("。配置的["),i("code",[e._v("AuthenticationEntryPoint")]),e._v("](../.../authentication/architecture.html# Servlet-authentication-authenticationentryPoint)是["),i("code",[e._v("LoginUrlAuthenticationEntryPoint")]),e._v("](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/authentication/loginurlauthenticationentrypoint.html)的一个实例,它会重定向到["),i("code",[e._v("")]),e._v("-genering-endpoint](authenticationcentification-exposit或者,如果你已经"),i("a",{attrs:{href:"#servlet-saml2login-relyingpartyregistrationrepository"}},[e._v("配置了多个断言对象")]),e._v(",它将首先重定向到一个选择器页面。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_4.png",alt:"number 4"}}),e._v("下一步,"),i("code",[e._v("Saml2WebSsoAuthenticationRequestFilter")]),e._v("使用其配置的["),i("code",[e._v("Saml2AuthenticationRequestFactory")]),e._v("](# Servlet-saml2login-sp-initiated-factory)创建、签名、序列化和编码"),i("code",[e._v("")]),e._v("。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_5.png",alt:"number 5"}}),e._v("然后,浏览器接收这个"),i("code",[e._v("")]),e._v("并将其呈现给断言的一方。断言一方试图对用户进行身份验证。如果成功,它将返回一个"),i("code",[e._v("")]),e._v("返回到浏览器。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_6.png",alt:"number 6"}}),e._v("浏览器然后将"),i("code",[e._v("")]),e._v("发布到断言消费者服务端点。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/servlet/saml2/saml2webssoauthenticationfilter.png",alt:"SAML2WebssoAuthenticationFilter"}})]),e._v(" "),i("p",[e._v("图2.验证"),i("code",[e._v("")])]),e._v(" "),i("p",[e._v("这个图是基于我们的["),i("code",[e._v("SecurityFilterChain")]),e._v("](../../architecture.html# Servlet-SecurityFilterchain)图构建的。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_1.png",alt:"number 1"}}),e._v("当浏览器向应用程序提交"),i("code",[e._v("")]),e._v("时,它[委托给"),i("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v("](authentication.html# Servlet-saml2login-attenticate-responses)。该过滤器调用其配置的"),i("code",[e._v("AuthenticationConverter")]),e._v(",通过从"),i("code",[e._v("HttpServletRequest")]),e._v("中提取响应来创建"),i("code",[e._v("Saml2AuthenticationToken")]),e._v("。该转换器还解析["),i("code",[e._v("重新注册政党")]),e._v("](# Servlet-saml2login-relyingpartyregistration)并将其提供给"),i("code",[e._v("Saml2AuthenticationToken")]),e._v("。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_2.png",alt:"number 2"}}),e._v("接下来,筛选器将令牌传递给其配置的["),i("code",[e._v("AuthenticationManager")]),e._v("](.../authentication/architecture.html# Servlet-authentication-providermanager)。默认情况下,它将使用["),i("code",[e._v("OpenSAML authentication provider")]),e._v("](# Servlet-saml2login-architecture)。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_3.png",alt:"number 3"}}),e._v("如果身份验证失败,则"),i("em",[e._v("失败")])]),e._v(" "),i("ul",[i("li",[i("p",[e._v("["),i("code",[e._v("SecurityContextHolder")]),e._v("](../../authentication/architecture.html# Servlet-authentication-securitycontextholder)被清除。")])]),e._v(" "),i("li",[i("p",[e._v("调用["),i("code",[e._v("AuthenticationEntryPoint")]),e._v("](../../authentication/architecture.html# Servlet-authentication-authenticationentryPoint)来重新启动身份验证过程。")])])]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_4.png",alt:"number 4"}}),e._v("如果身份验证成功,则"),i("em",[e._v("成功")]),e._v("。")]),e._v(" "),i("ul",[i("li",[i("p",[e._v("["),i("code",[e._v("Authentication")]),e._v("](../../authentication/architecture.html# Servlet-authentication-authentication)设置在["),i("code",[e._v("SecurityContextHolder")]),e._v("](../.../authentication/architecture.html# Servlet-authentication-securitycontexolder)上。")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v("调用"),i("code",[e._v("FilterChain#doFilter(request,response)")]),e._v("以继续应用程序逻辑的其余部分。")])])]),e._v(" "),i("h2",{attrs:{id:"最小依赖"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#最小依赖"}},[e._v("#")]),e._v(" 最小依赖")]),e._v(" "),i("p",[e._v("SAML2.0服务提供者支持驻留在"),i("code",[e._v("spring-security-saml2-service-provider")]),e._v("中。它是在OpenSAML库的基础上构建的。")]),e._v(" "),i("h2",{attrs:{id:"最小配置"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#最小配置"}},[e._v("#")]),e._v(" 最小配置")]),e._v(" "),i("p",[e._v("当使用"),i("a",{attrs:{href:"https://spring.io/projects/spring-boot",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Boot"),i("OutboundLink")],1),e._v("时,将应用程序配置为服务提供者包括两个基本步骤。首先,包括所需的依赖关系,其次,指示必要的断言方元数据。")]),e._v(" "),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("另外,这也意味着你已经"),i("RouterLink",{attrs:{to:"/metadata.html#servlet-saml2login-metadata"}},[e._v("向你的主张方登记了依赖方。")]),e._v("了。")],1)])]),e._v(" "),i("tbody")]),e._v(" "),i("h3",{attrs:{id:"指定身份提供程序元数据"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#指定身份提供程序元数据"}},[e._v("#")]),e._v(" 指定身份提供程序元数据")]),e._v(" "),i("p",[e._v("在 Spring 引导应用程序中,要指定身份提供者的元数据,只需执行以下操作:")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('spring:\n security:\n saml2:\n relyingparty:\n registration:\n adfs:\n identityprovider:\n entity-id: https://idp.example.com/issuer\n verification.credentials:\n - certificate-location: "classpath:idp.crt"\n singlesignon.url: https://idp.example.com/issuer/sso\n singlesignon.sign-request: false\n')])])]),i("p",[e._v("哪里")]),e._v(" "),i("ul",[i("li",[i("p",[i("code",[e._v("[https://idp.example.com/issuer](https://idp.example.com/issuer)")]),e._v("是身份提供程序将发出的SAML响应的"),i("code",[e._v("Issuer")]),e._v("属性中包含的值")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("classpath:idp.crt")]),e._v("是 Classpath 上用于验证SAML响应的身份提供程序证书的位置,并且")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("[https://idp.example.com/issuer/sso](https://idp.example.com/issuer/sso)")]),e._v("是标识提供程序期望"),i("code",[e._v("AuthnRequest")]),e._v("s的端点。")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("adfs")]),e._v("是"),i("a",{attrs:{href:"#servlet-saml2login-relyingpartyregistrationid"}},[e._v("你选择的任意标识符")])])])]),e._v(" "),i("p",[e._v("就这样!")]),e._v(" "),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("标识提供者和声明方是同义词,服务提供者和依赖方也是同义词。"),i("br"),e._v("它们通常分别缩写为AP和RP。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("h3",{attrs:{id:"运行时期望"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#运行时期望"}},[e._v("#")]),e._v(" 运行时期望")]),e._v(" "),i("p",[e._v("按照上面的配置,应用程序处理任何"),i("code",[e._v("POST /login/saml2/sso/{registrationId}")]),e._v("请求,其中包含"),i("code",[e._v("SAMLResponse")]),e._v("参数:")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("POST /login/saml2/sso/adfs HTTP/1.1\n\nSAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...\n")])])]),i("p",[e._v("有两种方法可以看到诱导你的断言一方生成"),i("code",[e._v("SAMLResponse")]),e._v(":")]),e._v(" "),i("ul",[i("li",[i("p",[e._v("首先,你可以导航到你的主张的一方。它可能为每个注册的依赖方提供了某种类型的链接或按钮,你可以单击这些链接或按钮发送"),i("code",[e._v("SAMLResponse")]),e._v("。")])]),e._v(" "),i("li",[i("p",[e._v("其次,你可以导航到应用程序中的受保护页面,例如"),i("code",[e._v("[http://localhost:8080](http://localhost:8080)")]),e._v("。然后,你的应用程序将重定向到配置的断言对象,然后发送"),i("code",[e._v("SAMLResponse")]),e._v("。")])])]),e._v(" "),i("p",[e._v("从这里开始,考虑跳到:")]),e._v(" "),i("ul",[i("li",[i("p",[i("a",{attrs:{href:"#servlet-saml2login-architecture"}},[e._v("SAML2.0Login如何与OpenSAML集成")])])]),e._v(" "),i("li",[i("p",[e._v("[如何使用"),i("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v("](authentication.html# Servlet-saml2login-authenticatedprincipal)")])]),e._v(" "),i("li",[i("p",[i("a",{attrs:{href:"#servlet-saml2login-sansboot"}},[e._v("How to Override or Replace Spring Boot’s Auto Configuration")])])])]),e._v(" "),i("h2",{attrs:{id:"saml2-0login如何与opensaml集成"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#saml2-0login如何与opensaml集成"}},[e._v("#")]),e._v(" SAML2.0Login如何与OpenSAML集成")]),e._v(" "),i("p",[e._v("Spring Security的SAML2.0支持有两个设计目标:")]),e._v(" "),i("ul",[i("li",[i("p",[e._v("首先,依赖SAML2.0操作和域对象的库。为了实现这一点, Spring Security使用了OpenSAML。")])]),e._v(" "),i("li",[i("p",[e._v("其次,确保在使用 Spring Security的SAML支持时不需要这个库。为了实现这一点, Spring Security在契约中使用OpenSAML的任何接口或类都保持封装。这使得你可以将OpenSAML转换为其他一些库,甚至是OpenSAML的不支持版本。")])])]),e._v(" "),i("p",[e._v("作为上述两个目标的自然结果, Spring Security的SAML API相对于其他模块来说非常小。相反,像"),i("code",[e._v("OpenSaml4AuthenticationRequestFactory")]),e._v("和"),i("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v("这样的类暴露了在身份验证过程中定制各种步骤的"),i("code",[e._v("Converter")]),e._v("s。")]),e._v(" "),i("p",[e._v("例如,一旦应用程序接收到"),i("code",[e._v("SAMLResponse")]),e._v("并委托给"),i("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v(",过滤器将委托给"),i("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v("。")]),e._v(" "),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("对于向后兼容性, Spring Security默认情况下将使用最新的OpenSAML3.,注意,尽管OpenSAML3已经达到了使用寿命,建议更新到OpenSAML4.x。,由于这个原因, Spring Security同时支持OpenSAML3.x和4.x。,如果你将OpenSAML依赖关系管理到4.x,")]),e._v("和任何"),i("code",[e._v("")]),e._v("条件。如果任何验证失败,则验证失败。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_9.png",alt:"number 9"}}),e._v("之后,提供者获取第一个断言的"),i("code",[e._v("AttributeStatement")]),e._v(",并将其映射到"),i("code",[e._v("Map>")]),e._v("。它还授予"),i("code",[e._v("ROLE_USER")]),e._v("授权。")]),e._v(" "),i("p",[i("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_10.png",alt:"number 10"}}),e._v("最后,它从第一个断言、属性的"),i("code",[e._v("Map")]),e._v("和"),i("code",[e._v("GrantedAuthority")]),e._v("中获取"),i("code",[e._v("NameID")]),e._v(",并构造"),i("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v("。然后,它把委托人和当权者归为"),i("code",[e._v("Saml2Authentication")]),e._v("。")]),e._v(" "),i("p",[e._v("得到的"),i("code",[e._v("Authentication#getPrincipal")]),e._v("是一个 Spring security"),i("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v("对象,并且"),i("code",[e._v("Authentication#getName")]),e._v("映射到第一个断言的"),i("code",[e._v("NameID")]),e._v("元素。"),i("code",[e._v("Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId")]),e._v("保存了关联"),i("code",[e._v("RelyingPartyRegistration")]),e._v("的[标识符](# Servlet-saml2login-relyingpartyregistrationID)。")]),e._v(" "),i("h3",{attrs:{id:"定制opensaml配置"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#定制opensaml配置"}},[e._v("#")]),e._v(" 定制OpenSAML配置")]),e._v(" "),i("p",[e._v("同时使用 Spring Security和OpenSAML的任何类都应该在类的开头静态初始化"),i("code",[e._v("OpenSamlInitializationService")]),e._v(",如下所示:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("static {\n\tOpenSamlInitializationService.initialize();\n}\n")])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("companion object {\n init {\n OpenSamlInitializationService.initialize()\n }\n}\n")])])]),i("p",[e._v("这取代了OpenSAML的"),i("code",[e._v("InitializationService#initialize")]),e._v("。")]),e._v(" "),i("p",[e._v("有时,定制OpenSAML构建、Marshalls和unmarshallsSAML对象的方式是很有价值的。在这种情况下,你可能希望调用"),i("code",[e._v("OpenSamlInitializationService#requireInitialize(Consumer)")]),e._v(",这样就可以访问OpenSAML的"),i("code",[e._v("XMLObjectProviderFactory")]),e._v("。")]),e._v(" "),i("p",[e._v("例如,当发送未签名的authnrequest时,你可能希望强制进行重新身份验证。在这种情况下,你可以注册自己的"),i("code",[e._v("AuthnRequestMarshaller")]),e._v(",如下所示:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("static {\n OpenSamlInitializationService.requireInitialize(factory -> {\n AuthnRequestMarshaller marshaller = new AuthnRequestMarshaller() {\n @Override\n public Element marshall(XMLObject object, Element element) throws MarshallingException {\n configureAuthnRequest((AuthnRequest) object);\n return super.marshall(object, element);\n }\n\n public Element marshall(XMLObject object, Document document) throws MarshallingException {\n configureAuthnRequest((AuthnRequest) object);\n return super.marshall(object, document);\n }\n\n private void configureAuthnRequest(AuthnRequest authnRequest) {\n authnRequest.setForceAuthn(true);\n }\n }\n\n factory.getMarshallerFactory().registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller);\n });\n}\n")])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("companion object {\n init {\n OpenSamlInitializationService.requireInitialize {\n val marshaller = object : AuthnRequestMarshaller() {\n override fun marshall(xmlObject: XMLObject, element: Element): Element {\n configureAuthnRequest(xmlObject as AuthnRequest)\n return super.marshall(xmlObject, element)\n }\n\n override fun marshall(xmlObject: XMLObject, document: Document): Element {\n configureAuthnRequest(xmlObject as AuthnRequest)\n return super.marshall(xmlObject, document)\n }\n\n private fun configureAuthnRequest(authnRequest: AuthnRequest) {\n authnRequest.isForceAuthn = true\n }\n }\n it.marshallerFactory.registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller)\n }\n }\n}\n")])])]),i("p",[e._v("每个应用程序实例只能调用"),i("code",[e._v("requireInitialize")]),e._v("方法一次。")]),e._v(" "),i("h2",{attrs:{id:"覆盖或替换boot-auto配置"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#覆盖或替换boot-auto配置"}},[e._v("#")]),e._v(" 覆盖或替换Boot Auto配置")]),e._v(" "),i("p",[e._v("Spring Boot为依赖方生成两个"),i("code",[e._v("@Bean")]),e._v("s。")]),e._v(" "),i("p",[e._v("第一种是将应用程序配置为依赖方的"),i("code",[e._v("WebSecurityConfigurerAdapter")]),e._v("。当包含"),i("code",[e._v("spring-security-saml2-service-provider")]),e._v("时,"),i("code",[e._v("WebSecurityConfigurerAdapter")]),e._v("看起来像:")]),e._v(" "),i("p",[e._v("例1.默认的JWT配置")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("protected void configure(HttpSecurity http) {\n http\n .authorizeHttpRequests(authorize -> authorize\n .anyRequest().authenticated()\n )\n .saml2Login(withDefaults());\n}\n")])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("fun configure(http: HttpSecurity) {\n http {\n authorizeRequests {\n authorize(anyRequest, authenticated)\n }\n saml2Login { }\n }\n}\n")])])]),i("p",[e._v("如果应用程序不公开"),i("code",[e._v("WebSecurityConfigurerAdapter")]),e._v(" Bean,那么 Spring 引导将公开上面的默认引导。")]),e._v(" "),i("p",[e._v("你可以通过在应用程序中公开 Bean 来替换此选项:")]),e._v(" "),i("p",[e._v("例2.自定义SAML2.0登录配置")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@EnableWebSecurity\npublic class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter {\n protected void configure(HttpSecurity http) {\n http\n .authorizeHttpRequests(authorize -> authorize\n .mvcMatchers("/messages/**").hasAuthority("ROLE_USER")\n .anyRequest().authenticated()\n )\n .saml2Login(withDefaults());\n }\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@EnableWebSecurity\nclass MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {\n override fun configure(http: HttpSecurity) {\n http {\n authorizeRequests {\n authorize("/messages/**", hasAuthority("ROLE_USER"))\n authorize(anyRequest, authenticated)\n }\n saml2Login {\n }\n }\n }\n}\n')])])]),i("p",[e._v("对于任何以"),i("code",[e._v("/messages/")]),e._v("开头的URL,上面要求"),i("code",[e._v("USER")]),e._v("的角色。")]),e._v(" "),i("p",[e._v("第二个"),i("code",[e._v("@Bean")]),e._v(" Spring 引导创建的是一个["),i("code",[e._v("RelyingPartyRegistrationRepository")]),e._v("](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/saml2/provider/service/registration/registrationpartyregistrationrepository.html),它代表了主张方和依赖方的元数据。这包括诸如依赖方在请求断言方验证时应使用的SSO端点的位置之类的内容。")]),e._v(" "),i("p",[e._v("你可以通过发布自己的"),i("code",[e._v("RelyingPartyRegistrationRepository")]),e._v(" Bean 来覆盖默认值。例如,你可以通过点击其元数据端点来查找断言一方的配置,如下所示:")]),e._v(" "),i("p",[e._v("例3.依赖方注册存储库")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@Value("${metadata.location}")\nString assertingPartyMetadataLocation;\n\n@Bean\npublic RelyingPartyRegistrationRepository relyingPartyRegistrations() {\n RelyingPartyRegistration registration = RelyingPartyRegistrations\n .fromMetadataLocation(assertingPartyMetadataLocation)\n .registrationId("example")\n .build();\n return new InMemoryRelyingPartyRegistrationRepository(registration);\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@Value("\\${metadata.location}")\nvar assertingPartyMetadataLocation: String? = null\n\n@Bean\nopen fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {\n val registration = RelyingPartyRegistrations\n .fromMetadataLocation(assertingPartyMetadataLocation)\n .registrationId("example")\n .build()\n return InMemoryRelyingPartyRegistrationRepository(registration)\n}\n')])])]),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[i("code",[e._v("registrationId")]),e._v("是你选择的用于区分注册的任意值。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("p",[e._v("或者,你可以手动提供每个细节,如下所示:")]),e._v(" "),i("p",[e._v("例4.依赖方注册存储库手动配置")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@Value("${verification.key}")\nFile verificationKey;\n\n@Bean\npublic RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {\n X509Certificate certificate = X509Support.decodeCertificate(this.verificationKey);\n Saml2X509Credential credential = Saml2X509Credential.verification(certificate);\n RelyingPartyRegistration registration = RelyingPartyRegistration\n .withRegistrationId("example")\n .assertingPartyDetails(party -> party\n .entityId("https://idp.example.com/issuer")\n .singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")\n .wantAuthnRequestsSigned(false)\n .verificationX509Credentials(c -> c.add(credential))\n )\n .build();\n return new InMemoryRelyingPartyRegistrationRepository(registration);\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@Value("\\${verification.key}")\nvar verificationKey: File? = null\n\n@Bean\nopen fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository {\n val certificate: X509Certificate? = X509Support.decodeCertificate(verificationKey!!)\n val credential: Saml2X509Credential = Saml2X509Credential.verification(certificate)\n val registration = RelyingPartyRegistration\n .withRegistrationId("example")\n .assertingPartyDetails { party: AssertingPartyDetails.Builder ->\n party\n .entityId("https://idp.example.com/issuer")\n .singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")\n .wantAuthnRequestsSigned(false)\n .verificationX509Credentials { c: MutableCollection ->\n c.add(\n credential\n )\n }\n }\n .build()\n return InMemoryRelyingPartyRegistrationRepository(registration)\n}\n')])])]),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("请注意,"),i("code",[e._v("X509Support")]),e._v("是一个OpenSAML类,在这里的代码片段中使用它是为了简洁")])])]),e._v(" "),i("tbody")]),e._v(" "),i("p",[e._v("或者,你可以使用DSL直接连接存储库,这也将覆盖自动配置的"),i("code",[e._v("WebSecurityConfigurerAdapter")]),e._v(":")]),e._v(" "),i("p",[e._v("例5.自定义依赖方注册DSL")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@EnableWebSecurity\npublic class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter {\n protected void configure(HttpSecurity http) {\n http\n .authorizeHttpRequests(authorize -> authorize\n .mvcMatchers("/messages/**").hasAuthority("ROLE_USER")\n .anyRequest().authenticated()\n )\n .saml2Login(saml2 -> saml2\n .relyingPartyRegistrationRepository(relyingPartyRegistrations())\n );\n }\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('@EnableWebSecurity\nclass MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {\n override fun configure(http: HttpSecurity) {\n http {\n authorizeRequests {\n authorize("/messages/**", hasAuthority("ROLE_USER"))\n authorize(anyRequest, authenticated)\n }\n saml2Login {\n relyingPartyRegistrationRepository = relyingPartyRegistrations()\n }\n }\n }\n}\n')])])]),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("通过在"),i("code",[e._v("RelyingPartyRegistrationRepository")]),e._v("中注册多个依赖方,依赖方可以是多租户。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("h2",{attrs:{id:"relyingpartyregistration"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#relyingpartyregistration"}},[e._v("#")]),e._v(" RelyingPartyRegistration")]),e._v(" "),i("p",[e._v("["),i("code",[e._v("RelyingPartyRegistration")]),e._v("](https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/saml2/provider/service/registration/registration/relypartyregistration.html)实例表示依赖方和断言方元数据之间的链接。")]),e._v(" "),i("p",[e._v("在"),i("code",[e._v("RelyingPartyRegistration")]),e._v("中,你可以提供依赖方的元数据,比如它的"),i("code",[e._v("Issuer")]),e._v("值,它希望将SAML响应发送到该值,以及它为签名或解密有效负载而OWNS的任何凭据。")]),e._v(" "),i("p",[e._v("此外,你还可以提供断言方元数据,比如它的"),i("code",[e._v("Issuer")]),e._v("值(它期望向其发送authnrequests),以及它为依赖方验证或加密有效负载而OWNS的任何公共凭据。")]),e._v(" "),i("p",[e._v("以下"),i("code",[e._v("RelyingPartyRegistration")]),e._v("是大多数设置所需的最低要求:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations\n .fromMetadataLocation("https://ap.example.org/metadata")\n .registrationId("my-id")\n .build();\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('val relyingPartyRegistration = RelyingPartyRegistrations\n .fromMetadataLocation("https://ap.example.org/metadata")\n .registrationId("my-id")\n .build()\n')])])]),i("p",[e._v("请注意,你也可以从任意的"),i("code",[e._v("InputStream")]),e._v("源创建"),i("code",[e._v("RelyingPartyRegistration")]),e._v("。一个这样的例子是当元数据存储在数据库中时:")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('String xml = fromDatabase();\ntry (InputStream source = new ByteArrayInputStream(xml.getBytes())) {\n RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations\n .fromMetadata(source)\n .registrationId("my-id")\n .build();\n}\n')])])]),i("p",[e._v("尽管更复杂的设置也是可能的,比如:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")\n .entityId("{baseUrl}/{registrationId}")\n .decryptionX509Credentials(c -> c.add(relyingPartyDecryptingCredential()))\n .assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")\n .assertingPartyDetails(party -> party\n .entityId("https://ap.example.org")\n .verificationX509Credentials(c -> c.add(assertingPartyVerifyingCredential()))\n .singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")\n )\n .build();\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('val relyingPartyRegistration =\n RelyingPartyRegistration.withRegistrationId("my-id")\n .entityId("{baseUrl}/{registrationId}")\n .decryptionX509Credentials { c: MutableCollection ->\n c.add(relyingPartyDecryptingCredential())\n }\n .assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")\n .assertingPartyDetails { party -> party\n .entityId("https://ap.example.org")\n .verificationX509Credentials { c -> c.add(assertingPartyVerifyingCredential()) }\n .singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")\n }\n .build()\n')])])]),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("顶级元数据方法是关于依赖方的详细信息。"),i("br"),i("code",[e._v("assertingPartyDetails")]),e._v("中的方法是关于断言方的详细信息。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("依赖方期望SAML响应的位置是断言消费者服务位置。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("p",[e._v("依赖方的"),i("code",[e._v("entityId")]),e._v("的默认值是"),i("code",[e._v("{baseUrl}/saml2/service-provider-metadata/{registrationId}")]),e._v("。这是在配置主张方以了解你的依赖方时所需的值。")]),e._v(" "),i("p",[i("code",[e._v("assertionConsumerServiceLocation")]),e._v("的默认值是"),i("code",[e._v("/login/saml2/sso/{registrationId}")]),e._v("。默认情况下,它被映射到过滤器链中的["),i("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v("](# Servlet-SAML2login-Authentication-SAML2WebssoAuthenticationFilter)。")]),e._v(" "),i("h3",{attrs:{id:"uri模式"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#uri模式"}},[e._v("#")]),e._v(" URI模式")]),e._v(" "),i("p",[e._v("你可能在上面的示例中注意到了"),i("code",[e._v("{baseUrl}")]),e._v("和"),i("code",[e._v("{registrationId}")]),e._v("占位符。")]),e._v(" "),i("p",[e._v("这些对于生成URI非常有用。因此,依赖方的"),i("code",[e._v("entityId")]),e._v("和"),i("code",[e._v("assertionConsumerServiceLocation")]),e._v("支持以下占位符:")]),e._v(" "),i("ul",[i("li",[i("p",[i("code",[e._v("baseUrl")]),e._v("-已部署应用程序的方案、主机和端口")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("registrationId")]),e._v("-此依赖方的注册ID")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("baseScheme")]),e._v("-已部署应用程序的方案")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("baseHost")]),e._v("-已部署应用程序的主机")])]),e._v(" "),i("li",[i("p",[i("code",[e._v("basePort")]),e._v("-已部署应用程序的端口")])])]),e._v(" "),i("p",[e._v("例如,上面定义的"),i("code",[e._v("assertionConsumerServiceLocation")]),e._v("是:")]),e._v(" "),i("p",[i("code",[e._v("/my-login-endpoint/{registrationId}")])]),e._v(" "),i("p",[e._v("在已部署的应用程序中,它将转换为")]),e._v(" "),i("p",[i("code",[e._v("/my-login-endpoint/adfs")])]),e._v(" "),i("p",[e._v("上述"),i("code",[e._v("entityId")]),e._v("定义为:")]),e._v(" "),i("p",[i("code",[e._v("{baseUrl}/{registrationId}")])]),e._v(" "),i("p",[e._v("在已部署的应用程序中,它将转换为")]),e._v(" "),i("p",[i("code",[e._v("https://rp.example.com/adfs")])]),e._v(" "),i("h3",{attrs:{id:"证书"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#证书"}},[e._v("#")]),e._v(" 证书")]),e._v(" "),i("p",[e._v("你还可能注意到了所使用的凭据。")]),e._v(" "),i("p",[e._v("通常情况下,依赖方将使用相同的密钥对有效载荷进行签名和解密。或者,它将使用相同的密钥来验证有效负载并对其进行加密。")]),e._v(" "),i("p",[e._v("因此, Spring Security附带"),i("code",[e._v("Saml2X509Credential")]),e._v(",这是一种特定于SAML的凭据,它简化了针对不同用例配置相同密钥的过程。")]),e._v(" "),i("p",[e._v("至少,有必要获得申述方的证明,这样申述方的签名回复才能得到验证。")]),e._v(" "),i("p",[e._v("要构造一个"),i("code",[e._v("Saml2X509Credential")]),e._v("来验证来自断言一方的断言,你可以加载该文件并使用"),i("code",[e._v("CertificateFactory")]),e._v(",如下所示:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('Resource resource = new ClassPathResource("ap.crt");\ntry (InputStream is = resource.getInputStream()) {\n X509Certificate certificate = (X509Certificate)\n CertificateFactory.getInstance("X.509").generateCertificate(is);\n return Saml2X509Credential.verification(certificate);\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('val resource = ClassPathResource("ap.crt")\nresource.inputStream.use {\n return Saml2X509Credential.verification(\n CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate?\n )\n}\n')])])]),i("p",[e._v("让我们假设,主张权利的一方也将对主张进行加密。在这种情况下,依赖方将需要一个私钥来解密加密值。")]),e._v(" "),i("p",[e._v("在这种情况下,你将需要一个"),i("code",[e._v("RSAPrivateKey")]),e._v("及其对应的"),i("code",[e._v("X509Certificate")]),e._v("。你可以使用 Spring Security的"),i("code",[e._v("RsaKeyConverters")]),e._v("实用程序类加载第一个,然后像以前一样加载第二个:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('X509Certificate certificate = relyingPartyDecryptionCertificate();\nResource resource = new ClassPathResource("rp.crt");\ntry (InputStream is = resource.getInputStream()) {\n RSAPrivateKey rsa = RsaKeyConverters.pkcs8().convert(is);\n return Saml2X509Credential.decryption(rsa, certificate);\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('val certificate: X509Certificate = relyingPartyDecryptionCertificate()\nval resource = ClassPathResource("rp.crt")\nresource.inputStream.use {\n val rsa: RSAPrivateKey = RsaKeyConverters.pkcs8().convert(it)\n return Saml2X509Credential.decryption(rsa, certificate)\n}\n')])])]),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("当你将这些文件的位置指定为适当的 Spring 引导属性时, Spring 引导将为你执行这些转换。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("h3",{attrs:{id:"从请求中解决依赖方"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#从请求中解决依赖方"}},[e._v("#")]),e._v(" 从请求中解决依赖方")]),e._v(" "),i("p",[e._v("到目前为止, Spring Security通过在URI路径中查找注册ID来解析"),i("code",[e._v("RelyingPartyRegistration")]),e._v("。")]),e._v(" "),i("p",[e._v("你可能想要定制的原因有很多。其中:")]),e._v(" "),i("ul",[i("li",[i("p",[e._v("你可能知道你永远不会是一个多租户应用程序,因此希望有一个更简单的URL方案")])]),e._v(" "),i("li",[i("p",[e._v("你可以通过URI路径以外的方式来识别租户。")])])]),e._v(" "),i("p",[e._v("要定制解析"),i("code",[e._v("RelyingPartyRegistration")]),e._v("的方式,可以配置自定义的"),i("code",[e._v("RelyingPartyRegistrationResolver")]),e._v("。默认值从URI的Last PATH元素中查找注册ID,并在"),i("code",[e._v("RelyingPartyRegistrationRepository")]),e._v("中查找它。")]),e._v(" "),i("p",[e._v("你可以提供一个更简单的解析器,例如,它总是返回相同的依赖方:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('public class SingleRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {\n\n private final RelyingPartyRegistrationResolver delegate;\n\n public SingleRelyingPartyRegistrationResolver(RelyingPartyRegistrationRepository registrations) {\n this.delegate = new DefaultRelyingPartyRegistrationResolver(registrations);\n }\n\n @Override\n public RelyingPartyRegistration resolve(HttpServletRequest request, String registrationId) {\n return this.delegate.resolve(request, "single");\n }\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationResolver) : RelyingPartyRegistrationResolver {\n override fun resolve(request: HttpServletRequest?, registrationId: String?): RelyingPartyRegistration? {\n return this.delegate.resolve(request, "single")\n }\n}\n')])])]),i("p",[e._v("然后,你可以将此解析器提供给适当的过滤器,这些过滤器可以[产生"),i("code",[e._v("")]),e._v("s](authentication-requests.html# Servlet-saml2login-sp-initiated-factory),[authenticate"),i("code",[e._v("")]),e._v("s](authentication.html# Servlet-saml2login-authenticate-responses-responses),和[产生"),i("code",[e._v("")]),e._v("元数据](../metadata.html# Servlet-saml2")]),e._v(" "),i("table",[i("thead",[i("tr",[i("th"),e._v(" "),i("th",[e._v("请记住,如果你的"),i("code",[e._v("RelyingPartyRegistration")]),e._v("中有任何占位符,那么你的解析器实现应该解析它们。")])])]),e._v(" "),i("tbody")]),e._v(" "),i("h3",{attrs:{id:"重复的依赖方配置"}},[i("a",{staticClass:"header-anchor",attrs:{href:"#重复的依赖方配置"}},[e._v("#")]),e._v(" 重复的依赖方配置")]),e._v(" "),i("p",[e._v("当应用程序使用多个断言对象时,在"),i("code",[e._v("RelyingPartyRegistration")]),e._v("实例之间会重复一些配置:")]),e._v(" "),i("ul",[i("li",[i("p",[e._v("依赖方的"),i("code",[e._v("entityId")])])]),e._v(" "),i("li",[i("p",[e._v("其"),i("code",[e._v("assertionConsumerServiceLocation")]),e._v(",以及")])]),e._v(" "),i("li",[i("p",[e._v("它的凭据,例如它的签名或解密凭据")])])]),e._v(" "),i("p",[e._v("这种设置的好处是,对于某些身份提供者来说,证书的轮换可能比其他身份提供者更容易。")]),e._v(" "),i("p",[e._v("复制可以通过几种不同的方式来减轻。")]),e._v(" "),i("p",[e._v("首先,在YAML中,可以通过引用来缓解这种情况,例如:")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v("spring:\n security:\n saml2:\n relyingparty:\n okta:\n signing.credentials: &relying-party-credentials\n - private-key-location: classpath:rp.key\n certificate-location: classpath:rp.crt\n identityprovider:\n entity-id: ...\n azure:\n signing.credentials: *relying-party-credentials\n identityprovider:\n entity-id: ...\n")])])]),i("p",[e._v("其次,在数据库中,不需要复制"),i("code",[e._v("RelyingPartyRegistration")]),e._v("的模型。")]),e._v(" "),i("p",[e._v("第三,在 Java 中,你可以创建一个自定义的配置方法,例如:")]),e._v(" "),i("p",[e._v("Java")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('private RelyingPartyRegistration.Builder\n addRelyingPartyDetails(RelyingPartyRegistration.Builder builder) {\n\n Saml2X509Credential signingCredential = ...\n builder.signingX509Credentials(c -> c.addAll(signingCredential));\n // ... other relying party configurations\n}\n\n@Bean\npublic RelyingPartyRegistrationRepository relyingPartyRegistrations() {\n RelyingPartyRegistration okta = addRelyingPartyDetails(\n RelyingPartyRegistrations\n .fromMetadataLocation(oktaMetadataUrl)\n .registrationId("okta")).build();\n\n RelyingPartyRegistration azure = addRelyingPartyDetails(\n RelyingPartyRegistrations\n .fromMetadataLocation(oktaMetadataUrl)\n .registrationId("azure")).build();\n\n return new InMemoryRelyingPartyRegistrationRepository(okta, azure);\n}\n')])])]),i("p",[e._v("Kotlin")]),e._v(" "),i("div",{staticClass:"language- extra-class"},[i("pre",{pre:!0,attrs:{class:"language-text"}},[i("code",[e._v('private fun addRelyingPartyDetails(builder: RelyingPartyRegistration.Builder): RelyingPartyRegistration.Builder {\n val signingCredential: Saml2X509Credential = ...\n builder.signingX509Credentials { c: MutableCollection ->\n c.add(\n signingCredential\n )\n }\n // ... other relying party configurations\n}\n\n@Bean\nopen fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {\n val okta = addRelyingPartyDetails(\n RelyingPartyRegistrations\n .fromMetadataLocation(oktaMetadataUrl)\n .registrationId("okta")\n ).build()\n val azure = addRelyingPartyDetails(\n RelyingPartyRegistrations\n .fromMetadataLocation(oktaMetadataUrl)\n .registrationId("azure")\n ).build()\n return InMemoryRelyingPartyRegistrationRepository(okta, azure)\n}\n')])])]),i("p",[i("RouterLink",{attrs:{to:"/spring-security/index.html"}},[e._v("SAML2登录")]),i("RouterLink",{attrs:{to:"/spring-security/authentication-requests.html"}},[e._v("SAML2身份验证请求")])],1)])}),[],!1,null,null,null);t.default=n.exports}}]);