| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617 | 
							- [[servlet-saml2]]
 
- = SAML2
 
- :figures: servlet/saml2
 
- Spring Security provides comprehensive SAML 2 support.
 
- This section discusses how to integrate SAML 2 into your servlet based application.
 
- [[servlet-saml2login]]
 
- == SAML 2.0 Login
 
- The SAML 2.0 Login feature provides an application with the capability to act as a SAML 2.0 Relying Party, having users https://wiki.shibboleth.net/confluence/display/CONCEPT/FlowsAndConfig[log in] to the application by using their existing account at a SAML 2.0 Asserting Party (Okta, ADFS, etc).
 
- NOTE: SAML 2.0 Login is implemented by using the *Web Browser SSO Profile*, as specified in
 
- https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf#page=15[SAML 2 Profiles].
 
- [[servlet-saml2login-spring-security-history]]
 
- Since 2009, support for relying parties has existed as an https://github.com/spring-projects/spring-security-saml/tree/1e013b07a7772defd6a26fcfae187c9bf661ee8f#spring-saml[extension project].
 
- In 2019, the process began to port that into https://github.com/spring-projects/spring-security[Spring Security] proper.
 
- This process is similar to the one started in 2017 for xref:servlet/oauth2/index.adoc[Spring Security's OAuth 2.0 support].
 
- [NOTE]
 
- ====
 
- A working sample for {gh-samples-url}/servlet/spring-boot/java/saml2-login[SAML 2.0 Login] is available in the {gh-samples-url}[Spring Security Samples repository].
 
- ====
 
- Let's take a look at how SAML 2.0 Relying Party Authentication works within Spring Security.
 
- First, we see that, like xref:servlet/oauth2/oauth2-login.adoc#oauth2login[ OAuth 2.0 Login], Spring Security takes the user to a third-party for performing authentication.
 
- It does this through a series of redirects.
 
- .Redirecting to Asserting Party Authentication
 
- image::{figures}/saml2webssoauthenticationrequestfilter.png[]
 
- The figure above builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] and xref:servlet/authentication/architecture.adoc#servlet-authentication-abstractprocessingfilter[ `AbstractAuthenticationProcessingFilter`] diagrams:
 
- image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource `/private` for which it is not authorized.
 
- image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
 
- image:{icondir}/number_3.png[] Since the user lacks authorization, the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__.
 
- The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`] which redirects to <<servlet-saml2login-sp-initiated-factory,the `<saml2:AuthnRequest>` generating endpoint>>, `Saml2WebSsoAuthenticationRequestFilter`.
 
- Or, if you've <<servlet-saml2login-relyingpartyregistrationrepository,configured more than one asserting party>>, it will first redirect to a picker page.
 
- image:{icondir}/number_4.png[] Next, the `Saml2WebSsoAuthenticationRequestFilter` creates, signs, serializes, and encodes a `<saml2:AuthnRequest>` using its configured <<servlet-saml2login-sp-initiated-factory,`Saml2AuthenticationRequestFactory`>>.
 
- image:{icondir}/number_5.png[] Then, the browser takes this `<saml2:AuthnRequest>` and presents it to the asserting party.
 
- The asserting party attempts to authentication the user.
 
- If successful, it will return a `<saml2:Response>` back to the browser.
 
- image:{icondir}/number_6.png[] The browser then POSTs the `<saml2:Response>` to the assertion consumer service endpoint.
 
- [[servlet-saml2login-authentication-saml2webssoauthenticationfilter]]
 
- .Authenticating a `<saml2:Response>`
 
- image::{figures}/saml2webssoauthenticationfilter.png[]
 
- The figure builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram.
 
- image:{icondir}/number_1.png[] When the browser submits a `<saml2:Response>` to the application, it <<servlet-saml2login-authenticate-responses, delegates to `Saml2WebSsoAuthenticationFilter`>>.
 
- This filter calls its configured `AuthenticationConverter` to create a `Saml2AuthenticationToken` by extracting the response from the `HttpServletRequest`.
 
- This converter additionally resolves the <<servlet-saml2login-relyingpartyregistration, `RelyingPartyRegistration`>> and supplies it to `Saml2AuthenticationToken`.
 
- image:{icondir}/number_2.png[] Next, the filter passes the token to its configured xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`AuthenticationManager`].
 
- By default, it will use the <<servlet-saml2login-architecture,`OpenSAML authentication provider`>>.
 
- image:{icondir}/number_3.png[] If authentication fails, then __Failure__
 
- * The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[ `SecurityContextHolder`] is cleared out.
 
- * The xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is invoked to restart the authentication process.
 
- image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
 
- * The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[ `Authentication`] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[ `SecurityContextHolder`].
 
- * The `Saml2WebSsoAuthenticationFilter` invokes `FilterChain#doFilter(request,response)` to continue with the rest of the application logic.
 
- [[servlet-saml2login-minimaldependencies]]
 
- === Minimal Dependencies
 
- SAML 2.0 service provider support resides in `spring-security-saml2-service-provider`.
 
- It builds off of the OpenSAML library.
 
- [[servlet-saml2login-minimalconfiguration]]
 
- === Minimal Configuration
 
- When using https://spring.io/projects/spring-boot[Spring Boot], configuring an application as a service provider consists of two basic steps.
 
- First, include the needed dependencies and second, indicate the necessary asserting party metadata.
 
- [NOTE]
 
- Also, this presupposes that you've already <<servlet-saml2login-metadata, registered the relying party with your asserting party>>.
 
- ==== Specifying Identity Provider Metadata
 
- In a Spring Boot application, to specify an identity provider's metadata, simply do:
 
- [source,yml]
 
- ----
 
- spring:
 
-   security:
 
-     saml2:
 
-       relyingparty:
 
-         registration:
 
-           adfs:
 
-             identityprovider:
 
-               entity-id: https://idp.example.com/issuer
 
-               verification.credentials:
 
-                 - certificate-location: "classpath:idp.crt"
 
-               singlesignon.url: https://idp.example.com/issuer/sso
 
-               singlesignon.sign-request: false
 
- ----
 
- where
 
- * `https://idp.example.com/issuer` is the value contained in the `Issuer` attribute of the SAML responses that the identity provider will issue
 
- * `classpath:idp.crt` is the location on the classpath for the identity provider's certificate for verifying SAML responses, and
 
- * `https://idp.example.com/issuer/sso` is the endpoint where the identity provider is expecting `AuthnRequest` s.
 
- And that's it!
 
- [NOTE]
 
- Identity Provider and Asserting Party are synonymous, as are Service Provider and Relying Party.
 
- These are frequently abbreviated as AP and RP, respectively.
 
- ==== Runtime Expectations
 
- As configured above, the application processes any `+POST /login/saml2/sso/{registrationId}+` request containing a `SAMLResponse` parameter:
 
- [source,html]
 
- ----
 
- POST /login/saml2/sso/adfs HTTP/1.1
 
- SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...
 
- ----
 
- There are two ways to see induce your asserting party to generate a `SAMLResponse`:
 
- * First, you can navigate to your asserting party.
 
- It likely has some kind of link or button for each registered relying party that you can click to send the `SAMLResponse`.
 
- * Second, you can navigate to a protected page in your app, for example, `http://localhost:8080`.
 
- Your app then redirects to the configured asserting party which then sends the `SAMLResponse`.
 
- From here, consider jumping to:
 
- * <<servlet-saml2login-architecture,How SAML 2.0 Login Integrates with OpenSAML>>
 
- * <<servlet-saml2login-authenticatedprincipal,How to Use the `Saml2AuthenticatedPrincipal`>>
 
- * <<servlet-saml2login-sansboot,How to Override or Replace Spring Boot's Auto Configuration>>
 
- [[servlet-saml2login-architecture]]
 
- === How SAML 2.0 Login Integrates with OpenSAML
 
- Spring Security's SAML 2.0 support has a couple of design goals:
 
- * First, rely on a library for SAML 2.0 operations and domain objects.
 
- To achieve this, Spring Security uses OpenSAML.
 
- * Second, ensure this library is not required when using Spring Security's SAML support.
 
- To achieve this, any interfaces or classes where Spring Security uses OpenSAML in the contract remain encapsulated.
 
- This makes it possible for you to switch out OpenSAML for some other library or even an unsupported version of OpenSAML.
 
- As a natural outcome of the above two goals, Spring Security's SAML API is quite small relative to other modules.
 
- Instead, classes like `OpenSaml4AuthenticationRequestFactory` and `OpenSaml4AuthenticationProvider` expose `Converter` s that customize various steps in the authentication process.
 
- For example, once your application receives a `SAMLResponse` and delegates to `Saml2WebSsoAuthenticationFilter`, the filter will delegate to `OpenSaml4AuthenticationProvider`.
 
- [NOTE]
 
- For backward compatibility, Spring Security will use the latest OpenSAML 3 by default.
 
- Note, though that OpenSAML 3 has reached it's end-of-life and updating to OpenSAML 4.x is recommended.
 
- For that reason, Spring Security supports both OpenSAML 3.x and 4.x.
 
- If you manage your OpenSAML dependency to 4.x, then Spring Security will select its OpenSAML 4.x implementations.
 
- .Authenticating an OpenSAML `Response`
 
- image:{figures}/opensamlauthenticationprovider.png[]
 
- This figure builds off of the <<servlet-saml2login-authentication-saml2webssoauthenticationfilter,`Saml2WebSsoAuthenticationFilter` diagram>>.
 
- image:{icondir}/number_1.png[] The `Saml2WebSsoAuthenticationFilter` formulates the `Saml2AuthenticationToken` and invokes the xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`AuthenticationManager`].
 
- image:{icondir}/number_2.png[] The xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`AuthenticationManager`] invokes the OpenSAML authentication provider.
 
- image:{icondir}/number_3.png[] The authentication provider deserializes the response into an OpenSAML `Response` and checks its signature.
 
- If the signature is invalid, authentication fails.
 
- image:{icondir}/number_4.png[] Then, the provider <<servlet-saml2login-opensamlauthenticationprovider-decryption,decrypts any `EncryptedAssertion` elements>>.
 
- If any decryptions fail, authentication fails.
 
- image:{icondir}/number_5.png[] Next, the provider validates the response's `Issuer` and `Destination` values.
 
- If they don't match what's in the `RelyingPartyRegistration`, authentication fails.
 
- image:{icondir}/number_6.png[] After that, the provider verifies the signature of each `Assertion`.
 
- If any signature is invalid, authentication fails.
 
- Also, if neither the response nor the assertions have signatures, authentication fails.
 
- Either the response or all the assertions must have signatures.
 
- image:{icondir}/number_7.png[] Then, the provider <<servlet-saml2login-opensamlauthenticationprovider-decryption,decrypts any `EncryptedID` or `EncryptedAttribute` elements>>.
 
- If any decryptions fail, authentication fails.
 
- image:{icondir}/number_8.png[] Next, the provider validates each assertion's `ExpiresAt` and `NotBefore` timestamps, the `<Subject>` and any `<AudienceRestriction>` conditions.
 
- If any validations fail, authentication fails.
 
- image:{icondir}/number_9.png[] Following that, the provider takes the first assertion's `AttributeStatement` and maps it to a `Map<String, List<Object>>`.
 
- It also grants the `ROLE_USER` granted authority.
 
- image:{icondir}/number_10.png[] And finally, it takes the `NameID` from the first assertion, the `Map` of attributes, and the `GrantedAuthority` and constructs a `Saml2AuthenticatedPrincipal`.
 
- Then, it places that principal and the authorities into a `Saml2Authentication`.
 
- The resulting `Authentication#getPrincipal` is a Spring Security `Saml2AuthenticatedPrincipal` object, and `Authentication#getName` maps to the first assertion's `NameID` element.
 
- [[servlet-saml2login-opensaml-customization]]
 
- ==== Customizing OpenSAML Configuration
 
- Any class that uses both Spring Security and OpenSAML should statically initialize `OpenSamlInitializationService` at the beginning of the class, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- static {
 
- 	OpenSamlInitializationService.initialize();
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- companion object {
 
-     init {
 
-         OpenSamlInitializationService.initialize()
 
-     }
 
- }
 
- ----
 
- ====
 
- This replaces OpenSAML's `InitializationService#initialize`.
 
- Occasionally, it can be valuable to customize how OpenSAML builds, marshalls, and unmarshalls SAML objects.
 
- In these circumstances, you may instead want to call `OpenSamlInitializationService#requireInitialize(Consumer)` that gives you access to OpenSAML's `XMLObjectProviderFactory`.
 
- For example, when sending an unsigned AuthNRequest, you may want to force reauthentication.
 
- In that case, you can register your own `AuthnRequestMarshaller`, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- 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);
 
-     });
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- 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)
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- The `requireInitialize` method may only be called once per application instance.
 
- [[servlet-saml2login-sansboot]]
 
- === Overriding or Replacing Boot Auto Configuration
 
- There are two `@Bean` s that Spring Boot generates for a relying party.
 
- The first is a `WebSecurityConfigurerAdapter` that configures the app as a relying party.
 
- When including `spring-security-saml2-service-provider`, the `WebSecurityConfigurerAdapter` looks like:
 
- .Default JWT Configuration
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- protected void configure(HttpSecurity http) {
 
-     http
 
-         .authorizeRequests(authorize -> authorize
 
-             .anyRequest().authenticated()
 
-         )
 
-         .saml2Login(withDefaults());
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- fun configure(http: HttpSecurity) {
 
-     http {
 
-         authorizeRequests {
 
-             authorize(anyRequest, authenticated)
 
-         }
 
-         saml2Login { }
 
-     }
 
- }
 
- ----
 
- ====
 
- If the application doesn't expose a `WebSecurityConfigurerAdapter` bean, then Spring Boot will expose the above default one.
 
- You can replace this by exposing the bean within the application:
 
- .Custom SAML 2.0 Login Configuration
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @EnableWebSecurity
 
- public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
-     protected void configure(HttpSecurity http) {
 
-         http
 
-             .authorizeRequests(authorize -> authorize
 
-                 .mvcMatchers("/messages/**").hasAuthority("ROLE_USER")
 
-                 .anyRequest().authenticated()
 
-             )
 
-             .saml2Login(withDefaults());
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebSecurity
 
- class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
 
-     override fun configure(http: HttpSecurity) {
 
-         http {
 
-             authorizeRequests {
 
-                 authorize("/messages/**", hasAuthority("ROLE_USER"))
 
-                 authorize(anyRequest, authenticated)
 
-             }
 
-             saml2Login {
 
-             }
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- The above requires the role of `USER` for any URL that starts with `/messages/`.
 
- [[servlet-saml2login-relyingpartyregistrationrepository]]
 
- The second `@Bean` Spring Boot creates is a {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[`RelyingPartyRegistrationRepository`], which represents the asserting party and relying party metadata.
 
- This includes things like the location of the SSO endpoint the relying party should use when requesting authentication from the asserting party.
 
- You can override the default by publishing your own `RelyingPartyRegistrationRepository` bean.
 
- For example, you can look up the asserting party's configuration by hitting its metadata endpoint like so:
 
- .Relying Party Registration Repository
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Value("${metadata.location}")
 
- String assertingPartyMetadataLocation;
 
- @Bean
 
- public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
 
-     RelyingPartyRegistration registration = RelyingPartyRegistrations
 
-             .fromMetadataLocation(assertingPartyMetadataLocation)
 
-             .registrationId("example")
 
-             .build();
 
-     return new InMemoryRelyingPartyRegistrationRepository(registration);
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Value("\${metadata.location}")
 
- var assertingPartyMetadataLocation: String? = null
 
- @Bean
 
- open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
 
-     val registration = RelyingPartyRegistrations
 
-         .fromMetadataLocation(assertingPartyMetadataLocation)
 
-         .registrationId("example")
 
-         .build()
 
-     return InMemoryRelyingPartyRegistrationRepository(registration)
 
- }
 
- ----
 
- ====
 
- Or you can provide each detail manually, as you can see below:
 
- .Relying Party Registration Repository Manual Configuration
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @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);
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @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)
 
- }
 
- ----
 
- ====
 
- [NOTE]
 
- Note that `X509Support` is an OpenSAML class, used here in the snippet for brevity
 
- [[servlet-saml2login-relyingpartyregistrationrepository-dsl]]
 
- Alternatively, you can directly wire up the repository using the DSL, which will also override the auto-configured `WebSecurityConfigurerAdapter`:
 
- .Custom Relying Party Registration DSL
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @EnableWebSecurity
 
- public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
-     protected void configure(HttpSecurity http) {
 
-         http
 
-             .authorizeRequests(authorize -> authorize
 
-                 .mvcMatchers("/messages/**").hasAuthority("ROLE_USER")
 
-                 .anyRequest().authenticated()
 
-             )
 
-             .saml2Login(saml2 -> saml2
 
-                 .relyingPartyRegistrationRepository(relyingPartyRegistrations())
 
-             );
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebSecurity
 
- class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
 
-     override fun configure(http: HttpSecurity) {
 
-         http {
 
-             authorizeRequests {
 
-                 authorize("/messages/**", hasAuthority("ROLE_USER"))
 
-                 authorize(anyRequest, authenticated)
 
-             }
 
-             saml2Login {
 
-                 relyingPartyRegistrationRepository = relyingPartyRegistrations()
 
-             }
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- [NOTE]
 
- A relying party can be multi-tenant by registering more than one relying party in the `RelyingPartyRegistrationRepository`.
 
- [[servlet-saml2login-relyingpartyregistration]]
 
- === RelyingPartyRegistration
 
- A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`]
 
- instance represents a link between an relying party and assering party's metadata.
 
- In a `RelyingPartyRegistration`, you can provide relying party metadata like its `Issuer` value, where it expects SAML Responses to be sent to, and any credentials that it owns for the purposes of signing or decrypting payloads.
 
- Also, you can provide asserting party metadata like its `Issuer` value, where it expects AuthnRequests to be sent to, and any public credentials that it owns for the purposes of the relying party verifying or encrypting payloads.
 
- The following `RelyingPartyRegistration` is the minimum required for most setups:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
 
-         .fromMetadataLocation("https://ap.example.org/metadata")
 
-         .registrationId("my-id")
 
-         .build();
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val relyingPartyRegistration = RelyingPartyRegistrations
 
-     .fromMetadataLocation("https://ap.example.org/metadata")
 
-     .registrationId("my-id")
 
-     .build()
 
- ----
 
- ====
 
- Note that you can also create a `RelyingPartyRegistration` from an arbitrary `InputStream` source.
 
- One such example is when the metadata is stored in a database:
 
- [source,java]
 
- ----
 
- String xml = fromDatabase();
 
- try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
 
-     RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
 
-             .fromMetadata(source)
 
-             .registrationId("my-id")
 
-             .build();
 
- }
 
- ----
 
- Though a more sophisticated setup is also possible, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- 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();
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- 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()
 
- ----
 
- ====
 
- [TIP]
 
- The top-level metadata methods are details about the relying party.
 
- The methods inside `assertingPartyDetails` are details about the asserting party.
 
- [NOTE]
 
- The location where a relying party is expecting SAML Responses is the Assertion Consumer Service Location.
 
- The default for the relying party's `entityId` is `+{baseUrl}/saml2/service-provider-metadata/{registrationId}+`.
 
- This is this value needed when configuring the asserting party to know about your relying party.
 
- The default for the `assertionConsumerServiceLocation` is `+/login/saml2/sso/{registrationId}+`.
 
- It's mapped by default to <<servlet-saml2login-authentication-saml2webssoauthenticationfilter,`Saml2WebSsoAuthenticationFilter`>> in the filter chain.
 
- [[servlet-saml2login-rpr-uripatterns]]
 
- ==== URI Patterns
 
- You probably noticed in the above examples the `+{baseUrl}+` and `+{registrationId}+` placeholders.
 
- These are useful for generating URIs. As such, the relying party's `entityId` and `assertionConsumerServiceLocation` support the following placeholders:
 
- * `baseUrl` - the scheme, host, and port of a deployed application
 
- * `registrationId` - the registration id for this relying party
 
- * `baseScheme` - the scheme of a deployed application
 
- * `baseHost` - the host of a deployed application
 
- * `basePort` - the port of a deployed application
 
- For example, the `assertionConsumerServiceLocation` defined above was:
 
- `+/my-login-endpoint/{registrationId}+`
 
- which in a deployed application would translate to
 
- `+/my-login-endpoint/adfs+`
 
- The `entityId` above was defined as:
 
- `+{baseUrl}/{registrationId}+`
 
- which in a deployed application would translate to
 
- `+https://rp.example.com/adfs+`
 
- [[servlet-saml2login-rpr-credentials]]
 
- ==== Credentials
 
- You also likely noticed the credential that was used.
 
- Oftentimes, a relying party will use the same key to sign payloads as well as decrypt them.
 
- Or it will use the same key to verify payloads as well as encrypt them.
 
- Because of this, Spring Security ships with `Saml2X509Credential`, a SAML-specific credential that simplifies configuring the same key for different use cases.
 
- At a minimum, it's necessary to have a certificate from the asserting party so that the asserting party's signed responses can be verified.
 
- To construct a `Saml2X509Credential` that you'll use to verify assertions from the asserting party, you can load the file and use
 
- the `CertificateFactory` like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- Resource resource = new ClassPathResource("ap.crt");
 
- try (InputStream is = resource.getInputStream()) {
 
-     X509Certificate certificate = (X509Certificate)
 
-             CertificateFactory.getInstance("X.509").generateCertificate(is);
 
-     return Saml2X509Credential.verification(certificate);
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val resource = ClassPathResource("ap.crt")
 
- resource.inputStream.use {
 
-     return Saml2X509Credential.verification(
 
-         CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate?
 
-     )
 
- }
 
- ----
 
- ====
 
- Let's say that the asserting party is going to also encrypt the assertion.
 
- In that case, the relying party will need a private key to be able to decrypt the encrypted value.
 
- In that case, you'll need an `RSAPrivateKey` as well as its corresponding `X509Certificate`.
 
- You can load the first using Spring Security's `RsaKeyConverters` utility class and the second as you did before:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- 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);
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val certificate: X509Certificate = relyingPartyDecryptionCertificate()
 
- val resource = ClassPathResource("rp.crt")
 
- resource.inputStream.use {
 
-     val rsa: RSAPrivateKey = RsaKeyConverters.pkcs8().convert(it)
 
-     return Saml2X509Credential.decryption(rsa, certificate)
 
- }
 
- ----
 
- ====
 
- [TIP]
 
- When you specify the locations of these files as the appropriate Spring Boot properties, then Spring Boot will perform these conversions for you.
 
- [[servlet-saml2login-rpr-relyingpartyregistrationresolver]]
 
- ==== Resolving the Relying Party from the Request
 
- As seen so far, Spring Security resolves the `RelyingPartyRegistration` by looking for the registration id in the URI path.
 
- There are a number of reasons you may want to customize. Among them:
 
- * You may know that you will never be a multi-tenant application and so want to have a simpler URL scheme
 
- * You may identify tenants in a way other than by the URI path
 
- To customize the way that a `RelyingPartyRegistration` is resolved, you can configure a custom `Converter<HttpServletRequest, RelyingPartyRegistration>`.
 
- The default looks up the registration id from the URI's last path element and looks it up in your `RelyingPartyRegistrationRepository`.
 
- You can provide a simpler resolver that, for example, always returns the same relying party:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- public class SingleRelyingPartyRegistrationResolver
 
-         implements Converter<HttpServletRequest, RelyingPartyRegistration> {
 
-     @Override
 
-     public RelyingPartyRegistration convert(HttpServletRequest request) {
 
-         return this.relyingParty;
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- class SingleRelyingPartyRegistrationResolver : Converter<HttpServletRequest?, RelyingPartyRegistration?> {
 
-     override fun convert(request: HttpServletRequest?): RelyingPartyRegistration? {
 
-         return this.relyingParty
 
-     }
 
- }
 
- ----
 
- ====
 
- Then, you can provide this resolver to the appropriate filters that <<servlet-saml2login-sp-initiated-factory, produce `<saml2:AuthnRequest>` s>>, <<servlet-saml2login-authenticate-responses, authenticate `<saml2:Response>` s>>, and <<servlet-saml2login-metadata, produce `<saml2:SPSSODescriptor>` metadata>>.
 
- [NOTE]
 
- Remember that if you have any placeholders in your `RelyingPartyRegistration`, your resolver implementation should resolve them.
 
- [[servlet-saml2login-rpr-duplicated]]
 
- ==== Duplicated Relying Party Configurations
 
- When an application uses multiple asserting parties, some configuration is duplicated between `RelyingPartyRegistration` instances:
 
- * The relying party's `entityId`
 
- * Its `assertionConsumerServiceLocation`, and
 
- * Its credentials, for example its signing or decryption credentials
 
- What's nice about this setup is credentials may be more easily rotated for some identity providers vs others.
 
- The duplication can be alleviated in a few different ways.
 
- First, in YAML this can be alleviated with references, like so:
 
- [source,yaml]
 
- ----
 
- spring:
 
-   security:
 
-     saml2:
 
-       relyingparty:
 
-         okta:
 
-           signing.credentials: &relying-party-credentials
 
-             - private-key-location: classpath:rp.key
 
-             - certificate-location: classpath:rp.crt
 
-           identityprovider:
 
-             entity-id: ...
 
-         azure:
 
-           signing.credentials: *relying-party-credentials
 
-           identityprovider:
 
-             entity-id: ...
 
- ----
 
- Second, in a database, it's not necessary to replicate `RelyingPartyRegistration` 's model.
 
- Third, in Java, you can create a custom configuration method, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- 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);
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- 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)
 
- }
 
- ----
 
- ====
 
- [[servlet-saml2login-sp-initiated-factory]]
 
- === Producing `<saml2:AuthnRequest>` s
 
- As stated earlier, Spring Security's SAML 2.0 support produces a `<saml2:AuthnRequest>` to commence authentication with the asserting party.
 
- Spring Security achieves this in part by registering the `Saml2WebSsoAuthenticationRequestFilter` in the filter chain.
 
- This filter by default responds to endpoint `+/saml2/authenticate/{registrationId}+`.
 
- For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
 
- `https://rp.example.org/saml2/authenticate/ping`
 
- and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
 
- [[servlet-saml2login-sp-initiated-factory-signing]]
 
- ==== Changing How the `<saml2:AuthnRequest>` Gets Sent
 
- By default, Spring Security signs each `<saml2:AuthnRequest>` and send it as a GET to the asserting party.
 
- Many asserting parties don't require a signed `<saml2:AuthnRequest>`.
 
- This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
 
- .Not Requiring Signed AuthnRequests
 
- ====
 
- .Boot
 
- [source,yaml,role="primary"]
 
- ----
 
- spring:
 
-   security:
 
-     saml2:
 
-       relyingparty:
 
-         okta:
 
-           identityprovider:
 
-             entity-id: ...
 
-             singlesignon.sign-request: false
 
- ----
 
- .Java
 
- [source,java,role="secondary"]
 
- ----
 
- RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
 
-         // ...
 
-         .assertingPartyDetails(party -> party
 
-             // ...
 
-             .wantAuthnRequestsSigned(false)
 
-         )
 
-         .build();
 
- ----
 
- .Kotlin
 
- [source,java,role="secondary"]
 
- ----
 
- var relyingPartyRegistration: RelyingPartyRegistration =
 
-     RelyingPartyRegistration.withRegistrationId("okta")
 
-         // ...
 
-         .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
 
-                 // ...
 
-                 .wantAuthnRequestsSigned(false)
 
-         }
 
-         .build();
 
- ----
 
- ====
 
- Otherwise, you will need to specify a private key to `RelyingPartyRegistration#signingX509Credentials` so that Spring Security can sign the `<saml2:AuthnRequest>` before sending.
 
- [[servlet-saml2login-sp-initiated-factory-algorithm]]
 
- By default, Spring Security will sign the `<saml2:AuthnRequest>` using `rsa-sha256`, though some asserting parties will require a different algorithm, as indicated in their metadata.
 
- You can configure the algorithm based on the asserting party's <<servlet-saml2login-relyingpartyregistrationrepository,metadata using `RelyingPartyRegistrations`>>.
 
- Or, you can provide it manually:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- String metadataLocation = "classpath:asserting-party-metadata.xml";
 
- RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
 
-         // ...
 
-         .assertingPartyDetails((party) -> party
 
-             // ...
 
-             .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
 
-         )
 
-         .build();
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- var metadataLocation = "classpath:asserting-party-metadata.xml"
 
- var relyingPartyRegistration: RelyingPartyRegistration =
 
-     RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
 
-         // ...
 
-         .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
 
-                 // ...
 
-                 .signingAlgorithms { sign: MutableList<String?> ->
 
-                     sign.add(
 
-                         SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
 
-                     )
 
-                 }
 
-         }
 
-         .build();
 
- ----
 
- ====
 
- NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name.
 
- But, that's just for convenience.
 
- Since the datatype is `String`, you can supply the name of the algorithm directly.
 
- [[servlet-saml2login-sp-initiated-factory-binding]]
 
- Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed.
 
- This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
 
-         // ...
 
-         .assertingPartyDetails(party -> party
 
-             // ...
 
-             .singleSignOnServiceBinding(Saml2MessageBinding.POST)
 
-         )
 
-         .build();
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- var relyingPartyRegistration: RelyingPartyRegistration? =
 
-     RelyingPartyRegistration.withRegistrationId("okta")
 
-         // ...
 
-         .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
 
-             // ...
 
-             .singleSignOnServiceBinding(Saml2MessageBinding.POST)
 
-         }
 
-         .build()
 
- ----
 
- ====
 
- [[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
 
- ==== Customizing OpenSAML's `AuthnRequest` Instance
 
- There are a number of reasons that you may want to adjust an `AuthnRequest`.
 
- For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default.
 
- If you don't need information from the `HttpServletRequest` to make your decision, then the easiest way is to <<servlet-saml2login-opensaml-customization,register a custom `AuthnRequestMarshaller` with OpenSAML>>.
 
- This will give you access to post-process the `AuthnRequest` instance before it's serialized.
 
- But, if you do need something from the request, then you can use create a custom `Saml2AuthenticationRequestContext` implementation and then a `Converter<Saml2AuthenticationRequestContext, AuthnRequest>` to build an `AuthnRequest` yourself, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Component
 
- public class AuthnRequestConverter implements
 
-         Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
 
-     private final AuthnRequestBuilder authnRequestBuilder;
 
-     private final IssuerBuilder issuerBuilder;
 
-     // ... constructor
 
-     public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
 
-         MySaml2AuthenticationRequestContext myContext = (MySaml2AuthenticationRequestContext) context;
 
-         Issuer issuer = issuerBuilder.buildObject();
 
-         issuer.setValue(myContext.getIssuer());
 
-         AuthnRequest authnRequest = authnRequestBuilder.buildObject();
 
-         authnRequest.setIssuer(issuer);
 
-         authnRequest.setDestination(myContext.getDestination());
 
-         authnRequest.setAssertionConsumerServiceURL(myContext.getAssertionConsumerServiceUrl());
 
-         // ... additional settings
 
-         authRequest.setForceAuthn(myContext.getForceAuthn());
 
-         return authnRequest;
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Component
 
- class AuthnRequestConverter : Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
 
-     private val authnRequestBuilder: AuthnRequestBuilder? = null
 
-     private val issuerBuilder: IssuerBuilder? = null
 
-     // ... constructor
 
-     override fun convert(context: MySaml2AuthenticationRequestContext): AuthnRequest {
 
-         val myContext: MySaml2AuthenticationRequestContext = context
 
-         val issuer: Issuer = issuerBuilder.buildObject()
 
-         issuer.value = myContext.getIssuer()
 
-         val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
 
-         authnRequest.issuer = issuer
 
-         authnRequest.destination = myContext.getDestination()
 
-         authnRequest.assertionConsumerServiceURL = myContext.getAssertionConsumerServiceUrl()
 
-         // ... additional settings
 
-         authRequest.setForceAuthn(myContext.getForceAuthn())
 
-         return authnRequest
 
-     }
 
- }
 
- ----
 
- ====
 
- Then, you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and publish them as `@Bean` s:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
 
-     Saml2AuthenticationRequestContextResolver resolver =
 
-             new DefaultSaml2AuthenticationRequestContextResolver();
 
-     return request -> {
 
-         Saml2AuthenticationRequestContext context = resolver.resolve(request);
 
-         return new MySaml2AuthenticationRequestContext(context, request.getParameter("force") != null);
 
-     };
 
- }
 
- @Bean
 
- Saml2AuthenticationRequestFactory authenticationRequestFactory(
 
-         AuthnRequestConverter authnRequestConverter) {
 
-     OpenSaml4AuthenticationRequestFactory authenticationRequestFactory =
 
-             new OpenSaml4AuthenticationRequestFactory();
 
-     authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
 
-     return authenticationRequestFactory;
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
 
-     val resolver: Saml2AuthenticationRequestContextResolver = DefaultSaml2AuthenticationRequestContextResolver()
 
-     return Saml2AuthenticationRequestContextResolver { request: HttpServletRequest ->
 
-         val context = resolver.resolve(request)
 
-         MySaml2AuthenticationRequestContext(
 
-             context,
 
-             request.getParameter("force") != null
 
-         )
 
-     }
 
- }
 
- @Bean
 
- open fun authenticationRequestFactory(
 
-     authnRequestConverter: AuthnRequestConverter?
 
- ): Saml2AuthenticationRequestFactory? {
 
-     val authenticationRequestFactory = OpenSaml4AuthenticationRequestFactory()
 
-     authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
 
-     return authenticationRequestFactory
 
- }
 
- ----
 
- ====
 
- [[servlet-saml2login-authenticate-responses]]
 
- === Authenticating `<saml2:Response>` s
 
- To verify SAML 2.0 Responses, Spring Security uses <<servlet-saml2login-architecture,`OpenSaml4AuthenticationProvider`>> by default.
 
- You can configure this in a number of ways including:
 
- 1. Setting a clock skew to timestamp validation
 
- 2. Mapping the response to a list of `GrantedAuthority` instances
 
- 3. Customizing the strategy for validating assertions
 
- 4. Customizing the strategy for decrypting response and assertion elements
 
- To configure these, you'll use the `saml2Login#authenticationManager` method in the DSL.
 
- [[servlet-saml2login-opensamlauthenticationprovider-clockskew]]
 
- ==== Setting a Clock Skew
 
- It's not uncommon for the asserting and relying parties to have system clocks that aren't perfectly synchronized.
 
- For that reason, you can configure `OpenSaml4AuthenticationProvider` 's default assertion validator with some tolerance:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @EnableWebSecurity
 
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
-     @Override
 
-     protected void configure(HttpSecurity http) throws Exception {
 
-         OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
 
-         authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider
 
-                 .createDefaultAssertionValidator(assertionToken -> {
 
-                     Map<String, Object> params = new HashMap<>();
 
-                     params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());
 
-                     // ... other validation parameters
 
-                     return new ValidationContext(params);
 
-                 })
 
-         );
 
-         http
 
-             .authorizeRequests(authz -> authz
 
-                 .anyRequest().authenticated()
 
-             )
 
-             .saml2Login(saml2 -> saml2
 
-                 .authenticationManager(new ProviderManager(authenticationProvider))
 
-             );
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebSecurity
 
- open class SecurityConfig : WebSecurityConfigurerAdapter() {
 
-     override fun configure(http: HttpSecurity) {
 
-         val authenticationProvider = OpenSaml4AuthenticationProvider()
 
-         authenticationProvider.setAssertionValidator(
 
-             OpenSaml4AuthenticationProvider
 
-                 .createDefaultAssertionValidator(Converter<OpenSaml4AuthenticationProvider.AssertionToken, ValidationContext> {
 
-                     val params: MutableMap<String, Any> = HashMap()
 
-                     params[CLOCK_SKEW] =
 
-                         Duration.ofMinutes(10).toMillis()
 
-                     ValidationContext(params)
 
-                 })
 
-         )
 
-         http {
 
-             authorizeRequests {
 
-                 authorize(anyRequest, authenticated)
 
-             }
 
-             saml2Login {
 
-                 authenticationManager = ProviderManager(authenticationProvider)
 
-             }
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- [[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]]
 
- ==== Coordinating with a `UserDetailsService`
 
- Or, perhaps you would like to include user details from a legacy `UserDetailsService`.
 
- In that case, the response authentication converter can come in handy, as can be seen below:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @EnableWebSecurity
 
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
-     @Autowired
 
-     UserDetailsService userDetailsService;
 
-     @Override
 
-     protected void configure(HttpSecurity http) throws Exception {
 
-         OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
 
-         authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
 
-             Saml2Authentication authentication = OpenSaml4AuthenticationProvider
 
-                     .createDefaultResponseAuthenticationConverter() <1>
 
-                     .convert(responseToken);
 
-             Assertion assertion = responseToken.getResponse().getAssertions().get(0);
 
-             String username = assertion.getSubject().getNameID().getValue();
 
-             UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); <2>
 
-             return MySaml2Authentication(userDetails, authentication); <3>
 
-         });
 
-         http
 
-             .authorizeRequests(authz -> authz
 
-                 .anyRequest().authenticated()
 
-             )
 
-             .saml2Login(saml2 -> saml2
 
-                 .authenticationManager(new ProviderManager(authenticationProvider))
 
-             );
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebSecurity
 
- open class SecurityConfig : WebSecurityConfigurerAdapter() {
 
-     @Autowired
 
-     var userDetailsService: UserDetailsService? = null
 
-     override fun configure(http: HttpSecurity) {
 
-         val authenticationProvider = OpenSaml4AuthenticationProvider()
 
-         authenticationProvider.setResponseAuthenticationConverter { responseToken: OpenSaml4AuthenticationProvider.ResponseToken ->
 
-             val authentication = OpenSaml4AuthenticationProvider
 
-                 .createDefaultResponseAuthenticationConverter() <1>
 
-                 .convert(responseToken)
 
-             val assertion: Assertion = responseToken.response.assertions[0]
 
-             val username: String = assertion.subject.nameID.value
 
-             val userDetails = userDetailsService!!.loadUserByUsername(username) <2>
 
-             MySaml2Authentication(userDetails, authentication) <3>
 
-         }
 
-         http {
 
-             authorizeRequests {
 
-                 authorize(anyRequest, authenticated)
 
-             }
 
-             saml2Login {
 
-                 authenticationManager = ProviderManager(authenticationProvider)
 
-             }
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- <1> First, call the default converter, which extracts attributes and authorities from the response
 
- <2> Second, call the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[ `UserDetailsService`] using the relevant information
 
- <3> Third, return a custom authentication that includes the user details
 
- [NOTE]
 
- It's not required to call `OpenSaml4AuthenticationProvider` 's default authentication converter.
 
- It returns a `Saml2AuthenticatedPrincipal` containing the attributes it extracted from `AttributeStatement` s as well as the single `ROLE_USER` authority.
 
- [[servlet-saml2login-opensamlauthenticationprovider-additionalvalidation]]
 
- ==== Performing Additional Response Validation
 
- `OpenSaml4AuthenticationProvider` validates the `Issuer` and `Destination` values right after decrypting the `Response`.
 
- You can customize the validation by extending the default validator concatenating with your own response validator, or you can replace it entirely with yours.
 
- For example, you can throw a custom exception with any additional information available in the `Response` object, like so:
 
- [source,java]
 
- ----
 
- OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
 
- provider.setResponseValidator((responseToken) -> {
 
- 	Saml2ResponseValidatorResult result = OpenSamlAuthenticationProvider
 
- 		.createDefaultResponseValidator()
 
- 		.convert(responseToken)
 
- 		.concat(myCustomValidator.convert(responseToken));
 
- 	if (!result.getErrors().isEmpty()) {
 
- 		String inResponseTo = responseToken.getInResponseTo();
 
- 		throw new CustomSaml2AuthenticationException(result, inResponseTo);
 
- 	}
 
- 	return result;
 
- });
 
- ----
 
- ==== Performing Additional Assertion Validation
 
- `OpenSaml4AuthenticationProvider` performs minimal validation on SAML 2.0 Assertions.
 
- After verifying the signature, it will:
 
- 1. Validate `<AudienceRestriction>` and `<DelegationRestriction>` conditions
 
- 2. Validate `<SubjectConfirmation>` s, expect for any IP address information
 
- To perform additional validation, you can configure your own assertion validator that delegates to `OpenSaml4AuthenticationProvider` 's default and then performs its own.
 
- [[servlet-saml2login-opensamlauthenticationprovider-onetimeuse]]
 
- For example, you can use OpenSAML's `OneTimeUseConditionValidator` to also validate a `<OneTimeUse>` condition, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
 
- OneTimeUseConditionValidator validator = ...;
 
- provider.setAssertionValidator(assertionToken -> {
 
-     Saml2ResponseValidatorResult result = OpenSaml4AuthenticationProvider
 
-             .createDefaultAssertionValidator()
 
-             .convert(assertionToken);
 
-     Assertion assertion = assertionToken.getAssertion();
 
-     OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse();
 
-     ValidationContext context = new ValidationContext();
 
-     try {
 
-         if (validator.validate(oneTimeUse, assertion, context) == ValidationResult.VALID) {
 
-             return result;
 
-         }
 
-     } catch (Exception e) {
 
-         return result.concat(new Saml2Error(INVALID_ASSERTION, e.getMessage()));
 
-     }
 
-     return result.concat(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage()));
 
- });
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- var provider = OpenSaml4AuthenticationProvider()
 
- var validator: OneTimeUseConditionValidator = ...
 
- provider.setAssertionValidator { assertionToken ->
 
-     val result = OpenSaml4AuthenticationProvider
 
-         .createDefaultAssertionValidator()
 
-         .convert(assertionToken)
 
-     val assertion: Assertion = assertionToken.assertion
 
-     val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse
 
-     val context = ValidationContext()
 
-     try {
 
-         if (validator.validate(oneTimeUse, assertion, context) == ValidationResult.VALID) {
 
-             return@setAssertionValidator result
 
-         }
 
-     } catch (e: Exception) {
 
-         return@setAssertionValidator result.concat(Saml2Error(INVALID_ASSERTION, e.message))
 
-     }
 
-     result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))
 
- }
 
- ----
 
- ====
 
- [NOTE]
 
- While recommended, it's not necessary to call `OpenSaml4AuthenticationProvider` 's default assertion validator.
 
- A circumstance where you would skip it would be if you don't need it to check the `<AudienceRestriction>` or the `<SubjectConfirmation>` since you are doing those yourself.
 
- [[servlet-saml2login-opensamlauthenticationprovider-decryption]]
 
- ==== Customizing Decryption
 
- Spring Security decrypts `<saml2:EncryptedAssertion>`, `<saml2:EncryptedAttribute>`, and `<saml2:EncryptedID>` elements automatically by using the decryption <<servlet-saml2login-rpr-credentials,`Saml2X509Credential` instances>> registered in the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>>.
 
- `OpenSaml4AuthenticationProvider` exposes <<servlet-saml2login-architecture,two decryption strategies>>.
 
- The response decrypter is for decrypting encrypted elements of the `<saml2:Response>`, like `<saml2:EncryptedAssertion>`.
 
- The assertion decrypter is for decrypting encrypted elements of the `<saml2:Assertion>`, like `<saml2:EncryptedAttribute>` and `<saml2:EncryptedID>`.
 
- You can replace `OpenSaml4AuthenticationProvider`'s default decryption strategy with your own.
 
- For example, if you have a separate service that decrypts the assertions in a `<saml2:Response>`, you can use it instead like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- MyDecryptionService decryptionService = ...;
 
- OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
 
- provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val decryptionService: MyDecryptionService = ...
 
- val provider = OpenSaml4AuthenticationProvider()
 
- provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }
 
- ----
 
- ====
 
- If you are also decrypting individual elements in a `<saml2:Assertion>`, you can customize the assertion decrypter, too:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion()));
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) }
 
- ----
 
- ====
 
- NOTE: There are two separate decrypters since assertions can be signed separately from responses.
 
- Trying to decrypt a signed assertion's elements before signature verification may invalidate the signature.
 
- If your asserting party signs the response only, then it's safe to decrypt all elements using only the response decrypter.
 
- [[servlet-saml2login-authenticationmanager-custom]]
 
- ==== Using a Custom Authentication Manager
 
- [[servlet-saml2login-opensamlauthenticationprovider-authenticationmanager]]
 
- Of course, the `authenticationManager` DSL method can be also used to perform a completely custom SAML 2.0 authentication.
 
- This authentication manager should expect a `Saml2AuthenticationToken` object containing the SAML 2.0 Response XML data.
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @EnableWebSecurity
 
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
-     @Override
 
-     protected void configure(HttpSecurity http) throws Exception {
 
-         AuthenticationManager authenticationManager = new MySaml2AuthenticationManager(...);
 
-         http
 
-             .authorizeRequests(authorize -> authorize
 
-                 .anyRequest().authenticated()
 
-             )
 
-             .saml2Login(saml2 -> saml2
 
-                 .authenticationManager(authenticationManager)
 
-             )
 
-         ;
 
-     }
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @EnableWebSecurity
 
- open class SecurityConfig : WebSecurityConfigurerAdapter() {
 
-     override fun configure(http: HttpSecurity) {
 
-         val customAuthenticationManager: AuthenticationManager = MySaml2AuthenticationManager(...)
 
-         http {
 
-             authorizeRequests {
 
-                 authorize(anyRequest, authenticated)
 
-             }
 
-             saml2Login {
 
-                 authenticationManager = customAuthenticationManager
 
-             }
 
-         }
 
-     }
 
- }
 
- ----
 
- ====
 
- [[servlet-saml2login-authenticatedprincipal]]
 
- === Using `Saml2AuthenticatedPrincipal`
 
- With the relying party correctly configured for a given asserting party, it's ready to accept assertions.
 
- Once the relying party validates an assertion, the result is a `Saml2Authentication` with a `Saml2AuthenticatedPrincipal`.
 
- This means that you can access the principal in your controller like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- @Controller
 
- public class MainController {
 
- 	@GetMapping("/")
 
- 	public String index(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) {
 
- 		String email = principal.getFirstAttribute("email");
 
- 		model.setAttribute("email", email);
 
- 		return "index";
 
- 	}
 
- }
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Controller
 
- class MainController {
 
-     @GetMapping("/")
 
-     fun index(@AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, model: Model): String {
 
-         val email = principal.getFirstAttribute<String>("email")
 
-         model.setAttribute("email", email)
 
-         return "index"
 
-     }
 
- }
 
- ----
 
- ====
 
- [TIP]
 
- Because the SAML 2.0 specification allows for each attribute to have multiple values, you can either call `getAttribute` to get the list of attributes or `getFirstAttribute` to get the first in the list.
 
- `getFirstAttribute` is quite handy when you know that there is only one value.
 
- [[servlet-saml2login-metadata]]
 
- === Producing `<saml2:SPSSODescriptor>` Metadata
 
- You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver =
 
-         new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository);
 
- Saml2MetadataFilter filter = new Saml2MetadataFilter(
 
-         relyingPartyRegistrationResolver,
 
-         new OpenSamlMetadataResolver());
 
- http
 
-     // ...
 
-     .saml2Login(withDefaults())
 
-     .addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val relyingPartyRegistrationResolver: Converter<HttpServletRequest, RelyingPartyRegistration> =
 
-     DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository)
 
- val filter = Saml2MetadataFilter(
 
-     relyingPartyRegistrationResolver,
 
-     OpenSamlMetadataResolver()
 
- )
 
- http {
 
-     //...
 
-     saml2Login { }
 
-     addFilterBefore<Saml2WebSsoAuthenticationFilter>(filter)
 
- }
 
- ----
 
- ====
 
- You can use this metadata endpoint to register your relying party with your asserting party.
 
- This is often as simple as finding the correct form field to supply the metadata endpoint.
 
- By default, the metadata endpoint is `+/saml2/service-provider-metadata/{registrationId}+`.
 
- You can change this by calling the `setRequestMatcher` method on the filter:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"));
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"))
 
- ----
 
- ====
 
- ensuring that the `registrationId` hint is at the end of the path.
 
- Or, if you have registered a custom relying party registration resolver in the constructor, then you can specify a path without a `registrationId` hint, like so:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET"));
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata", "GET"))
 
- ----
 
- ====
 
- [[servlet-saml2login-logout]]
 
- === Performing Single Logout
 
- Spring Security does not yet support single logout.
 
- Generally speaking, though, you can achieve this by creating and registering a custom `LogoutSuccessHandler` and `RequestMatcher`:
 
- ====
 
- .Java
 
- [source,java,role="primary"]
 
- ----
 
- http
 
-     // ...
 
-     .logout(logout -> logout
 
-         .logoutSuccessHandler(myCustomSuccessHandler())
 
-         .logoutRequestMatcher(myRequestMatcher())
 
-     )
 
- ----
 
- .Kotlin
 
- [source,kotlin,role="secondary"]
 
- ----
 
- http {
 
-     logout {
 
-         // ...
 
-         logoutSuccessHandler = myCustomSuccessHandler()
 
-         logoutRequestMatcher = myRequestMatcher()
 
-     }
 
- }
 
- ----
 
- ====
 
- The success handler will send logout requests to the asserting party.
 
- The request matcher will detect logout requests from the asserting party.
 
 
  |