(window.webpackJsonp=window.webpackJsonp||[]).push([[297],{724:function(e,t,a){"use strict";a.r(t);var i=a(56),n=Object(i.a)({},(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"saml-2-0-login-overview"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#saml-2-0-login-overview"}},[e._v("#")]),e._v(" SAML 2.0 Login Overview")]),e._v(" "),a("p",[e._v("Let’s take a look at how SAML 2.0 Relying Party Authentication works within Spring Security.\nFirst, we see that, like "),a("RouterLink",{attrs:{to:"/oauth2/login/index.html"}},[e._v("OAuth 2.0 Login")]),e._v(", Spring Security takes the user to a third-party for performing authentication.\nIt does this through a series of redirects.")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/servlet/saml2/saml2webssoauthenticationrequestfilter.png",alt:"saml2webssoauthenticationrequestfilter"}})]),e._v(" "),a("p",[e._v("Figure 1. Redirecting to Asserting Party Authentication")]),e._v(" "),a("p",[e._v("The figure above builds off our "),a("RouterLink",{attrs:{to:"/architecture.html#servlet-securityfilterchain"}},[a("code",[e._v("SecurityFilterChain")])]),e._v(" and "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-abstractprocessingfilter"}},[a("code",[e._v("AbstractAuthenticationProcessingFilter")])]),e._v(" diagrams:")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_1.png",alt:"number 1"}}),e._v(" First, a user makes an unauthenticated request to the resource "),a("code",[e._v("/private")]),e._v(" for which it is not authorized.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_2.png",alt:"number 2"}}),e._v(" Spring Security’s "),a("RouterLink",{attrs:{to:"/authorization/authorize-requests.html#servlet-authorization-filtersecurityinterceptor"}},[a("code",[e._v("FilterSecurityInterceptor")])]),e._v(" indicates that the unauthenticated request is "),a("em",[e._v("Denied")]),e._v(" by throwing an "),a("code",[e._v("AccessDeniedException")]),e._v(".")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_3.png",alt:"number 3"}}),e._v(" Since the user lacks authorization, the "),a("RouterLink",{attrs:{to:"/architecture.html#servlet-exceptiontranslationfilter"}},[a("code",[e._v("ExceptionTranslationFilter")])]),e._v(" initiates "),a("em",[e._v("Start Authentication")]),e._v(".\nThe configured "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-authenticationentrypoint"}},[a("code",[e._v("AuthenticationEntryPoint")])]),e._v(" is an instance of "),a("a",{attrs:{href:"https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html",target:"_blank",rel:"noopener noreferrer"}},[a("code",[e._v("LoginUrlAuthenticationEntryPoint")]),a("OutboundLink")],1),e._v(" which redirects to "),a("RouterLink",{attrs:{to:"/en/spring-security/authentication-requests.html#servlet-saml2login-sp-initiated-factory"}},[e._v("the "),a("code",[e._v("")]),e._v(" generating endpoint")]),e._v(", "),a("code",[e._v("Saml2WebSsoAuthenticationRequestFilter")]),e._v(".\nOr, if you’ve "),a("a",{attrs:{href:"#servlet-saml2login-relyingpartyregistrationrepository"}},[e._v("configured more than one asserting party")]),e._v(", it will first redirect to a picker page.")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_4.png",alt:"number 4"}}),e._v(" Next, the "),a("code",[e._v("Saml2WebSsoAuthenticationRequestFilter")]),e._v(" creates, signs, serializes, and encodes a "),a("code",[e._v("")]),e._v(" using its configured "),a("a",{attrs:{href:"#servlet-saml2login-sp-initiated-factory"}},[a("code",[e._v("Saml2AuthenticationRequestFactory")])]),e._v(".")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_5.png",alt:"number 5"}}),e._v(" Then, the browser takes this "),a("code",[e._v("")]),e._v(" and presents it to the asserting party.\nThe asserting party attempts to authentication the user.\nIf successful, it will return a "),a("code",[e._v("")]),e._v(" back to the browser.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_6.png",alt:"number 6"}}),e._v(" The browser then POSTs the "),a("code",[e._v("")]),e._v(" to the assertion consumer service endpoint.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/servlet/saml2/saml2webssoauthenticationfilter.png",alt:"saml2webssoauthenticationfilter"}})]),e._v(" "),a("p",[e._v("Figure 2. Authenticating a "),a("code",[e._v("")])]),e._v(" "),a("p",[e._v("The figure builds off our "),a("RouterLink",{attrs:{to:"/architecture.html#servlet-securityfilterchain"}},[a("code",[e._v("SecurityFilterChain")])]),e._v(" diagram.")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_1.png",alt:"number 1"}}),e._v(" When the browser submits a "),a("code",[e._v("")]),e._v(" to the application, it "),a("RouterLink",{attrs:{to:"/en/spring-security/authentication.html#servlet-saml2login-authenticate-responses"}},[e._v("delegates to "),a("code",[e._v("Saml2WebSsoAuthenticationFilter")])]),e._v(".\nThis filter calls its configured "),a("code",[e._v("AuthenticationConverter")]),e._v(" to create a "),a("code",[e._v("Saml2AuthenticationToken")]),e._v(" by extracting the response from the "),a("code",[e._v("HttpServletRequest")]),e._v(".\nThis converter additionally resolves the "),a("a",{attrs:{href:"#servlet-saml2login-relyingpartyregistration"}},[a("code",[e._v("RelyingPartyRegistration")])]),e._v(" and supplies it to "),a("code",[e._v("Saml2AuthenticationToken")]),e._v(".")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_2.png",alt:"number 2"}}),e._v(" Next, the filter passes the token to its configured "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-providermanager"}},[a("code",[e._v("AuthenticationManager")])]),e._v(".\nBy default, it will use the "),a("a",{attrs:{href:"#servlet-saml2login-architecture"}},[a("code",[e._v("OpenSAML authentication provider")])]),e._v(".")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_3.png",alt:"number 3"}}),e._v(" If authentication fails, then "),a("em",[e._v("Failure")])]),e._v(" "),a("ul",[a("li",[a("p",[e._v("The "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-securitycontextholder"}},[a("code",[e._v("SecurityContextHolder")])]),e._v(" is cleared out.")],1)]),e._v(" "),a("li",[a("p",[e._v("The "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-authenticationentrypoint"}},[a("code",[e._v("AuthenticationEntryPoint")])]),e._v(" is invoked to restart the authentication process.")],1)])]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_4.png",alt:"number 4"}}),e._v(" If authentication is successful, then "),a("em",[e._v("Success")]),e._v(".")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("The "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-authentication"}},[a("code",[e._v("Authentication")])]),e._v(" is set on the "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-securitycontextholder"}},[a("code",[e._v("SecurityContextHolder")])]),e._v(".")],1)]),e._v(" "),a("li",[a("p",[e._v("The "),a("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v(" invokes "),a("code",[e._v("FilterChain#doFilter(request,response)")]),e._v(" to continue with the rest of the application logic.")])])]),e._v(" "),a("h2",{attrs:{id:"minimal-dependencies"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#minimal-dependencies"}},[e._v("#")]),e._v(" Minimal Dependencies")]),e._v(" "),a("p",[e._v("SAML 2.0 service provider support resides in "),a("code",[e._v("spring-security-saml2-service-provider")]),e._v(".\nIt builds off of the OpenSAML library.")]),e._v(" "),a("h2",{attrs:{id:"minimal-configuration"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#minimal-configuration"}},[e._v("#")]),e._v(" Minimal Configuration")]),e._v(" "),a("p",[e._v("When using "),a("a",{attrs:{href:"https://spring.io/projects/spring-boot",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Boot"),a("OutboundLink")],1),e._v(", configuring an application as a service provider consists of two basic steps.\nFirst, include the needed dependencies and second, indicate the necessary asserting party metadata.")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Also, this presupposes that you’ve already "),a("RouterLink",{attrs:{to:"/en/metadata.html#servlet-saml2login-metadata"}},[e._v("registered the relying party with your asserting party")]),e._v(".")],1)])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"specifying-identity-provider-metadata"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#specifying-identity-provider-metadata"}},[e._v("#")]),e._v(" Specifying Identity Provider Metadata")]),e._v(" "),a("p",[e._v("In a Spring Boot application, to specify an identity provider’s metadata, simply do:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("where")]),e._v(" "),a("ul",[a("li",[a("p",[a("code",[e._v("[https://idp.example.com/issuer](https://idp.example.com/issuer)")]),e._v(" is the value contained in the "),a("code",[e._v("Issuer")]),e._v(" attribute of the SAML responses that the identity provider will issue")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("classpath:idp.crt")]),e._v(" is the location on the classpath for the identity provider’s certificate for verifying SAML responses, and")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("[https://idp.example.com/issuer/sso](https://idp.example.com/issuer/sso)")]),e._v(" is the endpoint where the identity provider is expecting "),a("code",[e._v("AuthnRequest")]),e._v("s.")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("adfs")]),e._v(" is "),a("a",{attrs:{href:"#servlet-saml2login-relyingpartyregistrationid"}},[e._v("an arbitrary identifier you choose")])])])]),e._v(" "),a("p",[e._v("And that’s it!")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Identity Provider and Asserting Party are synonymous, as are Service Provider and Relying Party."),a("br"),e._v("These are frequently abbreviated as AP and RP, respectively.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"runtime-expectations"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#runtime-expectations"}},[e._v("#")]),e._v(" Runtime Expectations")]),e._v(" "),a("p",[e._v("As configured above, the application processes any "),a("code",[e._v("POST /login/saml2/sso/{registrationId}")]),e._v(" request containing a "),a("code",[e._v("SAMLResponse")]),e._v(" parameter:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("POST /login/saml2/sso/adfs HTTP/1.1\n\nSAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...\n")])])]),a("p",[e._v("There are two ways to see induce your asserting party to generate a "),a("code",[e._v("SAMLResponse")]),e._v(":")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("First, you can navigate to your asserting party.\nIt likely has some kind of link or button for each registered relying party that you can click to send the "),a("code",[e._v("SAMLResponse")]),e._v(".")])]),e._v(" "),a("li",[a("p",[e._v("Second, you can navigate to a protected page in your app, for example, "),a("code",[e._v("[http://localhost:8080](http://localhost:8080)")]),e._v(".\nYour app then redirects to the configured asserting party which then sends the "),a("code",[e._v("SAMLResponse")]),e._v(".")])])]),e._v(" "),a("p",[e._v("From here, consider jumping to:")]),e._v(" "),a("ul",[a("li",[a("p",[a("a",{attrs:{href:"#servlet-saml2login-architecture"}},[e._v("How SAML 2.0 Login Integrates with OpenSAML")])])]),e._v(" "),a("li",[a("p",[a("RouterLink",{attrs:{to:"/en/spring-security/authentication.html#servlet-saml2login-authenticatedprincipal"}},[e._v("How to Use the "),a("code",[e._v("Saml2AuthenticatedPrincipal")])])],1)]),e._v(" "),a("li",[a("p",[a("a",{attrs:{href:"#servlet-saml2login-sansboot"}},[e._v("How to Override or Replace Spring Boot’s Auto Configuration")])])])]),e._v(" "),a("h2",{attrs:{id:"how-saml-2-0-login-integrates-with-opensaml"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#how-saml-2-0-login-integrates-with-opensaml"}},[e._v("#")]),e._v(" How SAML 2.0 Login Integrates with OpenSAML")]),e._v(" "),a("p",[e._v("Spring Security’s SAML 2.0 support has a couple of design goals:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("First, rely on a library for SAML 2.0 operations and domain objects.\nTo achieve this, Spring Security uses OpenSAML.")])]),e._v(" "),a("li",[a("p",[e._v("Second, ensure this library is not required when using Spring Security’s SAML support.\nTo achieve this, any interfaces or classes where Spring Security uses OpenSAML in the contract remain encapsulated.\nThis makes it possible for you to switch out OpenSAML for some other library or even an unsupported version of OpenSAML.")])])]),e._v(" "),a("p",[e._v("As a natural outcome of the above two goals, Spring Security’s SAML API is quite small relative to other modules.\nInstead, classes like "),a("code",[e._v("OpenSaml4AuthenticationRequestFactory")]),e._v(" and "),a("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" expose "),a("code",[e._v("Converter")]),e._v("s that customize various steps in the authentication process.")]),e._v(" "),a("p",[e._v("For example, once your application receives a "),a("code",[e._v("SAMLResponse")]),e._v(" and delegates to "),a("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v(", the filter will delegate to "),a("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(".")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("For backward compatibility, Spring Security will use the latest OpenSAML 3 by default."),a("br"),e._v("Note, though that OpenSAML 3 has reached it’s end-of-life and updating to OpenSAML 4.x is recommended."),a("br"),e._v("For that reason, Spring Security supports both OpenSAML 3.x and 4.x."),a("br"),e._v("If you manage your OpenSAML dependency to 4.x, then Spring Security will select its OpenSAML 4.x implementations.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Authenticating an OpenSAML "),a("code",[e._v("Response")])]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/servlet/saml2/opensamlauthenticationprovider.png",alt:"opensamlauthenticationprovider"}})]),e._v(" "),a("p",[e._v("This figure builds off of the "),a("a",{attrs:{href:"#servlet-saml2login-authentication-saml2webssoauthenticationfilter"}},[a("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v(" diagram")]),e._v(".")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_1.png",alt:"number 1"}}),e._v(" The "),a("code",[e._v("Saml2WebSsoAuthenticationFilter")]),e._v(" formulates the "),a("code",[e._v("Saml2AuthenticationToken")]),e._v(" and invokes the "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-providermanager"}},[a("code",[e._v("AuthenticationManager")])]),e._v(".")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_2.png",alt:"number 2"}}),e._v(" The "),a("RouterLink",{attrs:{to:"/authentication/architecture.html#servlet-authentication-providermanager"}},[a("code",[e._v("AuthenticationManager")])]),e._v(" invokes the OpenSAML authentication provider.")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_3.png",alt:"number 3"}}),e._v(" The authentication provider deserializes the response into an OpenSAML "),a("code",[e._v("Response")]),e._v(" and checks its signature.\nIf the signature is invalid, authentication fails.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_4.png",alt:"number 4"}}),e._v(" Then, the provider "),a("RouterLink",{attrs:{to:"/en/spring-security/authentication.html#servlet-saml2login-opensamlauthenticationprovider-decryption"}},[e._v("decrypts any "),a("code",[e._v("EncryptedAssertion")]),e._v(" elements")]),e._v(".\nIf any decryptions fail, authentication fails.")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_5.png",alt:"number 5"}}),e._v(" Next, the provider validates the response’s "),a("code",[e._v("Issuer")]),e._v(" and "),a("code",[e._v("Destination")]),e._v(" values.\nIf they don’t match what’s in the "),a("code",[e._v("RelyingPartyRegistration")]),e._v(", authentication fails.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_6.png",alt:"number 6"}}),e._v(" After that, the provider verifies the signature of each "),a("code",[e._v("Assertion")]),e._v(".\nIf any signature is invalid, authentication fails.\nAlso, if neither the response nor the assertions have signatures, authentication fails.\nEither the response or all the assertions must have signatures.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_7.png",alt:"number 7"}}),e._v(" Then, the provider "),a("RouterLink",{attrs:{to:"/en/spring-security/authentication.html#servlet-saml2login-opensamlauthenticationprovider-decryption"}},[e._v(",")]),e._v("decrypts any "),a("code",[e._v("EncryptedID")]),e._v(" or "),a("code",[e._v("EncryptedAttribute")]),e._v(" elements].\nIf any decryptions fail, authentication fails.")],1),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_8.png",alt:"number 8"}}),e._v(" Next, the provider validates each assertion’s "),a("code",[e._v("ExpiresAt")]),e._v(" and "),a("code",[e._v("NotBefore")]),e._v(" timestamps, the "),a("code",[e._v("")]),e._v(" and any "),a("code",[e._v("")]),e._v(" conditions.\nIf any validations fail, authentication fails.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_9.png",alt:"number 9"}}),e._v(" Following that, the provider takes the first assertion’s "),a("code",[e._v("AttributeStatement")]),e._v(" and maps it to a "),a("code",[e._v("Map>")]),e._v(".\nIt also grants the "),a("code",[e._v("ROLE_USER")]),e._v(" granted authority.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://docs.spring.io/spring-security/reference/_images/icons/number_10.png",alt:"number 10"}}),e._v(" And finally, it takes the "),a("code",[e._v("NameID")]),e._v(" from the first assertion, the "),a("code",[e._v("Map")]),e._v(" of attributes, and the "),a("code",[e._v("GrantedAuthority")]),e._v(" and constructs a "),a("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v(".\nThen, it places that principal and the authorities into a "),a("code",[e._v("Saml2Authentication")]),e._v(".")]),e._v(" "),a("p",[e._v("The resulting "),a("code",[e._v("Authentication#getPrincipal")]),e._v(" is a Spring Security "),a("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v(" object, and "),a("code",[e._v("Authentication#getName")]),e._v(" maps to the first assertion’s "),a("code",[e._v("NameID")]),e._v(" element."),a("code",[e._v("Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId")]),e._v(" holds the "),a("a",{attrs:{href:"#servlet-saml2login-relyingpartyregistrationid"}},[e._v("identifier to the associated "),a("code",[e._v("RelyingPartyRegistration")])]),e._v(".")]),e._v(" "),a("h3",{attrs:{id:"customizing-opensaml-configuration"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#customizing-opensaml-configuration"}},[e._v("#")]),e._v(" Customizing OpenSAML Configuration")]),e._v(" "),a("p",[e._v("Any class that uses both Spring Security and OpenSAML should statically initialize "),a("code",[e._v("OpenSamlInitializationService")]),e._v(" at the beginning of the class, like so:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("static {\n\tOpenSamlInitializationService.initialize();\n}\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("companion object {\n init {\n OpenSamlInitializationService.initialize()\n }\n}\n")])])]),a("p",[e._v("This replaces OpenSAML’s "),a("code",[e._v("InitializationService#initialize")]),e._v(".")]),e._v(" "),a("p",[e._v("Occasionally, it can be valuable to customize how OpenSAML builds, marshalls, and unmarshalls SAML objects.\nIn these circumstances, you may instead want to call "),a("code",[e._v("OpenSamlInitializationService#requireInitialize(Consumer)")]),e._v(" that gives you access to OpenSAML’s "),a("code",[e._v("XMLObjectProviderFactory")]),e._v(".")]),e._v(" "),a("p",[e._v("For example, when sending an unsigned AuthNRequest, you may want to force reauthentication.\nIn that case, you can register your own "),a("code",[e._v("AuthnRequestMarshaller")]),e._v(", like so:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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")])])]),a("p",[e._v("The "),a("code",[e._v("requireInitialize")]),e._v(" method may only be called once per application instance.")]),e._v(" "),a("h2",{attrs:{id:"overriding-or-replacing-boot-auto-configuration"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#overriding-or-replacing-boot-auto-configuration"}},[e._v("#")]),e._v(" Overriding or Replacing Boot Auto Configuration")]),e._v(" "),a("p",[e._v("There are two "),a("code",[e._v("@Bean")]),e._v("s that Spring Boot generates for a relying party.")]),e._v(" "),a("p",[e._v("The first is a "),a("code",[e._v("WebSecurityConfigurerAdapter")]),e._v(" that configures the app as a relying party.\nWhen including "),a("code",[e._v("spring-security-saml2-service-provider")]),e._v(", the "),a("code",[e._v("WebSecurityConfigurerAdapter")]),e._v(" looks like:")]),e._v(" "),a("p",[e._v("Example 1. Default JWT Configuration")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("protected void configure(HttpSecurity http) {\n http\n .authorizeHttpRequests(authorize -> authorize\n .anyRequest().authenticated()\n )\n .saml2Login(withDefaults());\n}\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("fun configure(http: HttpSecurity) {\n http {\n authorizeRequests {\n authorize(anyRequest, authenticated)\n }\n saml2Login { }\n }\n}\n")])])]),a("p",[e._v("If the application doesn’t expose a "),a("code",[e._v("WebSecurityConfigurerAdapter")]),e._v(" bean, then Spring Boot will expose the above default one.")]),e._v(" "),a("p",[e._v("You can replace this by exposing the bean within the application:")]),e._v(" "),a("p",[e._v("Example 2. Custom SAML 2.0 Login Configuration")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("The above requires the role of "),a("code",[e._v("USER")]),e._v(" for any URL that starts with "),a("code",[e._v("/messages/")]),e._v(".")]),e._v(" "),a("p",[e._v("The second "),a("code",[e._v("@Bean")]),e._v(" Spring Boot creates is a "),a("a",{attrs:{href:"https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html",target:"_blank",rel:"noopener noreferrer"}},[a("code",[e._v("RelyingPartyRegistrationRepository")]),a("OutboundLink")],1),e._v(", which represents the asserting party and relying party metadata.\nThis includes things like the location of the SSO endpoint the relying party should use when requesting authentication from the asserting party.")]),e._v(" "),a("p",[e._v("You can override the default by publishing your own "),a("code",[e._v("RelyingPartyRegistrationRepository")]),e._v(" bean.\nFor example, you can look up the asserting party’s configuration by hitting its metadata endpoint like so:")]),e._v(" "),a("p",[e._v("Example 3. Relying Party Registration Repository")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The "),a("code",[e._v("registrationId")]),e._v(" is an arbitrary value that you choose for differentiating between registrations.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Or you can provide each detail manually, as you can see below:")]),e._v(" "),a("p",[e._v("Example 4. Relying Party Registration Repository Manual Configuration")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Note that "),a("code",[e._v("X509Support")]),e._v(" is an OpenSAML class, used here in the snippet for brevity")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Alternatively, you can directly wire up the repository using the DSL, which will also override the auto-configured "),a("code",[e._v("WebSecurityConfigurerAdapter")]),e._v(":")]),e._v(" "),a("p",[e._v("Example 5. Custom Relying Party Registration DSL")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("A relying party can be multi-tenant by registering more than one relying party in the "),a("code",[e._v("RelyingPartyRegistrationRepository")]),e._v(".")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h2",{attrs:{id:"relyingpartyregistration"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#relyingpartyregistration"}},[e._v("#")]),e._v(" RelyingPartyRegistration")]),e._v(" "),a("p",[e._v("A "),a("a",{attrs:{href:"https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html",target:"_blank",rel:"noopener noreferrer"}},[a("code",[e._v("RelyingPartyRegistration")]),a("OutboundLink")],1),e._v("instance represents a link between an relying party and assering party’s metadata.")]),e._v(" "),a("p",[e._v("In a "),a("code",[e._v("RelyingPartyRegistration")]),e._v(", you can provide relying party metadata like its "),a("code",[e._v("Issuer")]),e._v(" value, where it expects SAML Responses to be sent to, and any credentials that it owns for the purposes of signing or decrypting payloads.")]),e._v(" "),a("p",[e._v("Also, you can provide asserting party metadata like its "),a("code",[e._v("Issuer")]),e._v(" 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.")]),e._v(" "),a("p",[e._v("The following "),a("code",[e._v("RelyingPartyRegistration")]),e._v(" is the minimum required for most setups:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations\n .fromMetadataLocation("https://ap.example.org/metadata")\n .registrationId("my-id")\n .build();\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('val relyingPartyRegistration = RelyingPartyRegistrations\n .fromMetadataLocation("https://ap.example.org/metadata")\n .registrationId("my-id")\n .build()\n')])])]),a("p",[e._v("Note that you can also create a "),a("code",[e._v("RelyingPartyRegistration")]),e._v(" from an arbitrary "),a("code",[e._v("InputStream")]),e._v(" source.\nOne such example is when the metadata is stored in a database:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Though a more sophisticated setup is also possible, like so:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The top-level metadata methods are details about the relying party."),a("br"),e._v("The methods inside "),a("code",[e._v("assertingPartyDetails")]),e._v(" are details about the asserting party.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The location where a relying party is expecting SAML Responses is the Assertion Consumer Service Location.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("The default for the relying party’s "),a("code",[e._v("entityId")]),e._v(" is "),a("code",[e._v("{baseUrl}/saml2/service-provider-metadata/{registrationId}")]),e._v(".\nThis is this value needed when configuring the asserting party to know about your relying party.")]),e._v(" "),a("p",[e._v("The default for the "),a("code",[e._v("assertionConsumerServiceLocation")]),e._v(" is "),a("code",[e._v("/login/saml2/sso/{registrationId}")]),e._v(".\nIt’s mapped by default to "),a("a",{attrs:{href:"#servlet-saml2login-authentication-saml2webssoauthenticationfilter"}},[a("code",[e._v("Saml2WebSsoAuthenticationFilter")])]),e._v(" in the filter chain.")]),e._v(" "),a("h3",{attrs:{id:"uri-patterns"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#uri-patterns"}},[e._v("#")]),e._v(" URI Patterns")]),e._v(" "),a("p",[e._v("You probably noticed in the above examples the "),a("code",[e._v("{baseUrl}")]),e._v(" and "),a("code",[e._v("{registrationId}")]),e._v(" placeholders.")]),e._v(" "),a("p",[e._v("These are useful for generating URIs. As such, the relying party’s "),a("code",[e._v("entityId")]),e._v(" and "),a("code",[e._v("assertionConsumerServiceLocation")]),e._v(" support the following placeholders:")]),e._v(" "),a("ul",[a("li",[a("p",[a("code",[e._v("baseUrl")]),e._v(" - the scheme, host, and port of a deployed application")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("registrationId")]),e._v(" - the registration id for this relying party")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("baseScheme")]),e._v(" - the scheme of a deployed application")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("baseHost")]),e._v(" - the host of a deployed application")])]),e._v(" "),a("li",[a("p",[a("code",[e._v("basePort")]),e._v(" - the port of a deployed application")])])]),e._v(" "),a("p",[e._v("For example, the "),a("code",[e._v("assertionConsumerServiceLocation")]),e._v(" defined above was:")]),e._v(" "),a("p",[a("code",[e._v("/my-login-endpoint/{registrationId}")])]),e._v(" "),a("p",[e._v("which in a deployed application would translate to")]),e._v(" "),a("p",[a("code",[e._v("/my-login-endpoint/adfs")])]),e._v(" "),a("p",[e._v("The "),a("code",[e._v("entityId")]),e._v(" above was defined as:")]),e._v(" "),a("p",[a("code",[e._v("{baseUrl}/{registrationId}")])]),e._v(" "),a("p",[e._v("which in a deployed application would translate to")]),e._v(" "),a("p",[a("code",[e._v("https://rp.example.com/adfs")])]),e._v(" "),a("h3",{attrs:{id:"credentials"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#credentials"}},[e._v("#")]),e._v(" Credentials")]),e._v(" "),a("p",[e._v("You also likely noticed the credential that was used.")]),e._v(" "),a("p",[e._v("Oftentimes, a relying party will use the same key to sign payloads as well as decrypt them.\nOr it will use the same key to verify payloads as well as encrypt them.")]),e._v(" "),a("p",[e._v("Because of this, Spring Security ships with "),a("code",[e._v("Saml2X509Credential")]),e._v(", a SAML-specific credential that simplifies configuring the same key for different use cases.")]),e._v(" "),a("p",[e._v("At a minimum, it’s necessary to have a certificate from the asserting party so that the asserting party’s signed responses can be verified.")]),e._v(" "),a("p",[e._v("To construct a "),a("code",[e._v("Saml2X509Credential")]),e._v(" that you’ll use to verify assertions from the asserting party, you can load the file and use\nthe "),a("code",[e._v("CertificateFactory")]),e._v(" like so:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Let’s say that the asserting party is going to also encrypt the assertion.\nIn that case, the relying party will need a private key to be able to decrypt the encrypted value.")]),e._v(" "),a("p",[e._v("In that case, you’ll need an "),a("code",[e._v("RSAPrivateKey")]),e._v(" as well as its corresponding "),a("code",[e._v("X509Certificate")]),e._v(".\nYou can load the first using Spring Security’s "),a("code",[e._v("RsaKeyConverters")]),e._v(" utility class and the second as you did before:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("When you specify the locations of these files as the appropriate Spring Boot properties, then Spring Boot will perform these conversions for you.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"resolving-the-relying-party-from-the-request"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#resolving-the-relying-party-from-the-request"}},[e._v("#")]),e._v(" Resolving the Relying Party from the Request")]),e._v(" "),a("p",[e._v("As seen so far, Spring Security resolves the "),a("code",[e._v("RelyingPartyRegistration")]),e._v(" by looking for the registration id in the URI path.")]),e._v(" "),a("p",[e._v("There are a number of reasons you may want to customize. Among them:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("You may know that you will never be a multi-tenant application and so want to have a simpler URL scheme")])]),e._v(" "),a("li",[a("p",[e._v("You may identify tenants in a way other than by the URI path")])])]),e._v(" "),a("p",[e._v("To customize the way that a "),a("code",[e._v("RelyingPartyRegistration")]),e._v(" is resolved, you can configure a custom "),a("code",[e._v("RelyingPartyRegistrationResolver")]),e._v(".\nThe default looks up the registration id from the URI’s last path element and looks it up in your "),a("code",[e._v("RelyingPartyRegistrationRepository")]),e._v(".")]),e._v(" "),a("p",[e._v("You can provide a simpler resolver that, for example, always returns the same relying party:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Then, you can provide this resolver to the appropriate filters that "),a("RouterLink",{attrs:{to:"/en/spring-security/authentication-requests.html#servlet-saml2login-sp-initiated-factory"}},[e._v("produce "),a("code",[e._v("")]),e._v("s")]),e._v(", "),a("RouterLink",{attrs:{to:"/en/spring-security/authentication.html#servlet-saml2login-authenticate-responses"}},[e._v("authenticate "),a("code",[e._v("")]),e._v("s")]),e._v(", and "),a("RouterLink",{attrs:{to:"/en/metadata.html#servlet-saml2login-metadata"}},[e._v("produce "),a("code",[e._v("")]),e._v(" metadata")]),e._v(".")],1),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Remember that if you have any placeholders in your "),a("code",[e._v("RelyingPartyRegistration")]),e._v(", your resolver implementation should resolve them.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"duplicated-relying-party-configurations"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#duplicated-relying-party-configurations"}},[e._v("#")]),e._v(" Duplicated Relying Party Configurations")]),e._v(" "),a("p",[e._v("When an application uses multiple asserting parties, some configuration is duplicated between "),a("code",[e._v("RelyingPartyRegistration")]),e._v(" instances:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("The relying party’s "),a("code",[e._v("entityId")])])]),e._v(" "),a("li",[a("p",[e._v("Its "),a("code",[e._v("assertionConsumerServiceLocation")]),e._v(", and")])]),e._v(" "),a("li",[a("p",[e._v("Its credentials, for example its signing or decryption credentials")])])]),e._v(" "),a("p",[e._v("What’s nice about this setup is credentials may be more easily rotated for some identity providers vs others.")]),e._v(" "),a("p",[e._v("The duplication can be alleviated in a few different ways.")]),e._v(" "),a("p",[e._v("First, in YAML this can be alleviated with references, like so:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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")])])]),a("p",[e._v("Second, in a database, it’s not necessary to replicate "),a("code",[e._v("RelyingPartyRegistration")]),e._v(" 's model.")]),e._v(" "),a("p",[e._v("Third, in Java, you can create a custom configuration method, like so:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("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')])])]),a("p",[a("RouterLink",{attrs:{to:"/en/spring-security/index.html"}},[e._v("SAML2 Log In")]),a("RouterLink",{attrs:{to:"/en/spring-security/authentication-requests.html"}},[e._v("SAML2 Authentication Requests")])],1)])}),[],!1,null,null,null);t.default=n.exports}}]);