Explorar o código

Separate SAML Docs

Issue gh-10367
Josh Cummings %!s(int64=3) %!d(string=hai) anos
pai
achega
f39d272a86

+ 3 - 0
docs/modules/ROOT/nav.adoc

@@ -63,6 +63,9 @@
 **** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
 **** xref:servlet/oauth2/resource-server/bearer-tokens.adoc[Bearer Tokens]
 ** xref:servlet/saml2/index.adoc[SAML2]
+*** xref:servlet/saml2/login.adoc[SAML2 Log In]
+*** xref:servlet/saml2/logout.adoc[SAML2 Logout]
+*** xref:servlet/saml2/metadata.adoc[SAML2 Metadata]
 ** xref:servlet/exploits/index.adoc[Protection Against Exploits]
 *** xref:servlet/exploits/csrf.adoc[]
 *** xref:servlet/exploits/headers.adoc[]

+ 1 - 1612
docs/modules/ROOT/pages/servlet/saml2/index.adoc

@@ -1,1617 +1,6 @@
 [[servlet-saml2]]
 = SAML2
-:figures: servlet/saml2
+:page-section-summary-toc: 1
 
 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.
-

+ 29 - 384
docs/modules/ROOT/pages/servlet/saml2/saml2-login.adoc → docs/modules/ROOT/pages/servlet/saml2/login.adoc

@@ -1,7 +1,5 @@
-
-
 [[servlet-saml2login]]
-== SAML 2.0 Login
+= SAML 2.0 Login
 :figures: images/servlet/saml2
 :icondir: images/icons
 
@@ -69,21 +67,21 @@ image:{icondir}/number_4.png[] If authentication is successful, then __Success__
 * The `Saml2WebSsoAuthenticationFilter` invokes `FilterChain#doFilter(request,response)` to continue with the rest of the application logic.
 
 [[servlet-saml2login-minimaldependencies]]
-=== Minimal Dependencies
+== 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
+== 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>>.
+Also, this presupposes that you've already xref:servlet/saml2/metadata.adoc#servlet-saml2login-metadata[registered the relying party with your asserting party].
 
-==== Specifying Identity Provider Metadata
+=== Specifying Identity Provider Metadata
 
 In a Spring Boot application, to specify an identity provider's metadata, simply do:
 
@@ -116,7 +114,7 @@ And that's it!
 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
+=== Runtime Expectations
 
 As configured above, the application processes any `+POST /login/saml2/sso/{registrationId}+` request containing a `SAMLResponse` parameter:
 
@@ -141,7 +139,7 @@ From here, consider jumping to:
 * <<servlet-saml2login-sansboot,How to Override or Replace Spring Boot's Auto Configuration>>
 
 [[servlet-saml2login-architecture]]
-=== How SAML 2.0 Login Integrates with OpenSAML
+== How SAML 2.0 Login Integrates with OpenSAML
 
 Spring Security's SAML 2.0 support has a couple of design goals:
 
@@ -201,7 +199,7 @@ The resulting `Authentication#getPrincipal` is a Spring Security `Saml2Authentic
 `Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId` holds the <<servlet-saml2login-relyingpartyregistrationid,identifier to the associated `RelyingPartyRegistration`>>.
 
 [[servlet-saml2login-opensaml-customization]]
-==== Customizing OpenSAML Configuration
+=== Customizing OpenSAML Configuration
 
 Any class that uses both Spring Security and OpenSAML should statically initialize `OpenSamlInitializationService` at the beginning of the class, like so:
 
@@ -293,7 +291,7 @@ companion object {
 The `requireInitialize` method may only be called once per application instance.
 
 [[servlet-saml2login-sansboot]]
-=== Overriding or Replacing Boot Auto Configuration
+== Overriding or Replacing Boot Auto Configuration
 
 There are two ``@Bean``s that Spring Boot generates for a relying party.
 
@@ -524,7 +522,7 @@ class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
 A relying party can be multi-tenant by registering more than one relying party in the `RelyingPartyRegistrationRepository`.
 
 [[servlet-saml2login-relyingpartyregistration]]
-=== 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.
 
@@ -618,7 +616,7 @@ The default for the `assertionConsumerServiceLocation` is `+/login/saml2/sso/{re
 It's mapped by default to <<servlet-saml2login-authentication-saml2webssoauthenticationfilter,`Saml2WebSsoAuthenticationFilter`>> in the filter chain.
 
 [[servlet-saml2login-rpr-uripatterns]]
-==== URI Patterns
+=== URI Patterns
 
 You probably noticed in the above examples the `+{baseUrl}+` and `+{registrationId}+` placeholders.
 
@@ -647,7 +645,7 @@ which in a deployed application would translate to
 `+https://rp.example.com/adfs+`
 
 [[servlet-saml2login-rpr-credentials]]
-==== Credentials
+=== Credentials
 
 You also likely noticed the credential that was used.
 
@@ -719,7 +717,7 @@ resource.inputStream.use {
 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
+=== 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.
 
@@ -763,13 +761,13 @@ class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationR
 ----
 ====
 
-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>>.
+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 xref:servlet/saml2/metadata.adoc#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
+=== Duplicated Relying Party Configurations
 
 When an application uses multiple asserting parties, some configuration is duplicated between `RelyingPartyRegistration` instances:
 
@@ -864,7 +862,7 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
 ====
 
 [[servlet-saml2login-sp-initiated-factory]]
-=== Producing ``<saml2:AuthnRequest>``s
+== 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.
 
@@ -878,7 +876,7 @@ For example, if you were deployed to `https://rp.example.com` and you gave your
 and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
 
 [[servlet-saml2login-store-authn-request]]
-==== Changing How the `<saml2:AuthnRequest>` Gets Stored
+=== Changing How the `<saml2:AuthnRequest>` Gets Stored
 
 `Saml2WebSsoAuthenticationRequestFilter` uses an `Saml2AuthenticationRequestRepository` to persist an `AbstractSaml2AuthenticationRequest` instance before <<servlet-saml2login-sp-initiated-factory,sending the `<saml2:AuthnRequest>`>> to the asserting party.
 
@@ -909,7 +907,7 @@ open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository
 ====
 
 [[servlet-saml2login-sp-initiated-factory-signing]]
-==== Changing How the `<saml2:AuthnRequest>` Gets Sent
+=== 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.
 
@@ -1036,7 +1034,7 @@ var relyingPartyRegistration: RelyingPartyRegistration? =
 ====
 
 [[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
-==== Customizing OpenSAML's `AuthnRequest` Instance
+=== 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.
@@ -1157,7 +1155,7 @@ open fun authenticationRequestFactory(
 ====
 
 [[servlet-saml2login-authenticate-responses]]
-=== Authenticating ``<saml2:Response>``s
+== Authenticating ``<saml2:Response>``s
 
 To verify SAML 2.0 Responses, Spring Security uses <<servlet-saml2login-architecture,`OpenSaml4AuthenticationProvider`>> by default.
 
@@ -1171,7 +1169,7 @@ You can configure this in a number of ways including:
 To configure these, you'll use the `saml2Login#authenticationManager` method in the DSL.
 
 [[servlet-saml2login-opensamlauthenticationprovider-clockskew]]
-==== Setting a Clock Skew
+=== 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:
@@ -1236,7 +1234,7 @@ open class SecurityConfig : WebSecurityConfigurerAdapter() {
 ====
 
 [[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]]
-==== Coordinating with a `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:
@@ -1314,7 +1312,7 @@ It's not required to call `OpenSaml4AuthenticationProvider` 's default authentic
 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
+=== 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.
@@ -1336,7 +1334,7 @@ provider.setResponseValidator((responseToken) -> {
 });
 ----
 
-==== Performing Additional Assertion Validation
+=== Performing Additional Assertion Validation
 `OpenSaml4AuthenticationProvider` performs minimal validation on SAML 2.0 Assertions.
 After verifying the signature, it will:
 
@@ -1362,7 +1360,7 @@ provider.setAssertionValidator(assertionToken -> {
     OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse();
     ValidationContext context = new ValidationContext();
     try {
-        if (validator.validate(oneTimeUse, assertion, context) == ValidationResult.VALID) {
+        if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {
             return result;
         }
     } catch (Exception e) {
@@ -1385,7 +1383,7 @@ provider.setAssertionValidator { assertionToken ->
     val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse
     val context = ValidationContext()
     try {
-        if (validator.validate(oneTimeUse, assertion, context) == ValidationResult.VALID) {
+        if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {
             return@setAssertionValidator result
         }
     } catch (e: Exception) {
@@ -1401,7 +1399,7 @@ While recommended, it's not necessary to call `OpenSaml4AuthenticationProvider`
 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
+=== 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`>>.
 
@@ -1451,7 +1449,7 @@ Trying to decrypt a signed assertion's elements before signature verification ma
 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
+=== 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.
@@ -1500,7 +1498,7 @@ open class SecurityConfig : WebSecurityConfigurerAdapter() {
 ====
 
 [[servlet-saml2login-authenticatedprincipal]]
-=== Using `Saml2AuthenticatedPrincipal`
+== 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`.
@@ -1540,356 +1538,3 @@ class MainController {
 [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"]
-----
-DefaultRelyingPartyRegistrationResolver 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"))
-----
-====
-
-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 ships with support for RP- and AP-initiated SAML 2.0 Single Logout.
-
-Briefly, there are two use cases Spring Security supports:
-
-* **RP-Initiated** - Your application has an endpoint that, when POSTed to, will logout the user and send a `saml2:LogoutRequest` to the asserting party.
-Thereafter, the asserting party will send back a `saml2:LogoutResponse` and allow your application to respond
-* **AP-Initiated** - Your application has an endpoint that will receive a `saml2:LogoutRequest` from the asserting party.
-Your application will complete its logout at that point and then send a `saml2:LogoutResponse` to the asserting party.
-
-[NOTE]
-In the **AP-Initiated** scenario, any local redirection that your application would do post-logout is rendered moot.
-Once your application sends a `saml2:LogoutResponse`, it no longer has control of the browser.
-
-=== Minimal Configuration for Single Logout
-
-To use Spring Security's SAML 2.0 Single Logout feature, you will need the following things:
-
-* First, the asserting party must support SAML 2.0 Single Logout
-* Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
-* Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
-
-You can begin from the initial minimal example and add the following configuration:
-
-[source,java]
-----
-@Value("${private.key}") RSAPrivateKey key;
-@Value("${public.certificate}") X509Certificate certificate;
-
-@Bean
-RelyingPartyRegistrationRepository registrations() {
-    Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
-    RelyingPartyRegistration registration = RelyingPartyRegistrations
-            .fromMetadataLocation("https://ap.example.org/metadata")
-            .registrationId("id")
-            .signingX509Credentials((signing) -> signing.add(credential)) <1>
-            .build();
-    return new InMemoryRelyingPartyRegistrationRepository(registration);
-}
-
-@Bean
-SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
-    http
-        .authorizeRequests((authorize) -> authorize
-            .anyRequest().authenticated()
-        )
-        .saml2Login(withDefaults())
-        .saml2Logout(withDefaults()); <2>
-
-    return http.build();
-}
-----
-<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <<servlet-saml2login-rpr-duplicated,multiple instances>>
-<2> - Second, indicate that your application wants to use SAML SLO to logout the end user
-
-==== Runtime Expectations
-
-Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
-Your application will then do the following:
-
-1. Logout the user and invalidate the session
-2. Use a `Saml2LogoutRequestResolver` to create, sign, and serialize a `<saml2:LogoutRequest>` based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>> associated with the currently logged-in user.
-3. Send a redirect or post to the asserting party based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>>
-4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
-5. Redirect to any configured successful logout endpoint
-
-Also, your application can participate in an AP-initiated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
-
-1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
-2. Logout the user and invalidate the session
-3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>> associated with the just logged-out user
-4. Send a redirect or post to the asserting party based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>>
-
-=== Configuring Logout Endpoints
-
-There are three behaviors that can be triggered by different endpoints:
-
-* RP-initiated logout, which allows an authenticated user to `POST` and trigger the logout process by sending the asserting party a `<saml2:LogoutRequest>`
-* AP-initiated logout, which allows an asserting party to send a `<saml2:LogoutRequest>` to the application
-* AP logout response, which allows an asserting party to send a `<saml2:LogoutResponse>` in response to the RP-initiated `<saml2:LogoutRequest>`
-
-The first is triggered by performing normal `POST /logout` when the principal is of type `Saml2AuthenticatedPrincipal`.
-
-The second is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLRequest` signed by the asserting party.
-
-The third is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLResponse` signed by the asserting party.
-
-Because the user is already logged in or the original Logout Request is known, the `registrationId` is already known.
-For this reason, `+{registrationId}+` is not part of these URLs by default.
-
-This URL is customizable in the DSL.
-
-For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to `GET /SLOService.saml2`.
-To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so:
-
-====
-.Java
-[source,java,role="primary"]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
-        .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
-    );
-----
-====
-
-You should also configure these endpoints in your `RelyingPartyRegistration`.
-
-=== Customizing `<saml2:LogoutRequest>` Resolution
-
-It's common to need to set other values in the `<saml2:LogoutRequest>` than the defaults that Spring Security provides.
-
-By default, Spring Security will issue a `<saml2:LogoutRequest>` and supply:
-
-* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation`
-* The `ID` attribute - a GUID
-* The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
-* The `<NameID>` element - from `Authentication#getName`
-
-To add other values, you can use delegation, like so:
-
-[source,java]
-----
-@Bean
-Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationResolver registrationResolver) {
-	OpenSaml4LogoutRequestResolver logoutRequestResolver
-			new OpenSaml4LogoutRequestResolver(registrationResolver);
-	logoutRequestResolver.setParametersConsumer((parameters) -> {
-		String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
-		String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
-		LogoutRequest logoutRequest = parameters.getLogoutRequest();
-		NameID nameId = logoutRequest.getNameID();
-		nameId.setValue(name);
-		nameId.setFormat(format);
-	});
-	return logoutRequestResolver;
-}
-----
-
-Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestResolver(this.logoutRequestResolver)
-        )
-    );
-----
-
-=== Customizing `<saml2:LogoutResponse>` Resolution
-
-It's common to need to set other values in the `<saml2:LogoutResponse>` than the defaults that Spring Security provides.
-
-By default, Spring Security will issue a `<saml2:LogoutResponse>` and supply:
-
-* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation`
-* The `ID` attribute - a GUID
-* The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
-* The `<Status>` element - `SUCCESS`
-
-To add other values, you can use delegation, like so:
-
-[source,java]
-----
-@Bean
-public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationResolver registrationResolver) {
-	OpenSaml4LogoutResponseResolver logoutRequestResolver =
-			new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
-	logoutRequestResolver.setParametersConsumer((parameters) -> {
-		if (checkOtherPrevailingConditions(parameters.getRequest())) {
-			parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
-		}
-	});
-	return logoutRequestResolver;
-}
-----
-
-Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestResolver(this.logoutRequestResolver)
-        )
-    );
-----
-
-=== Customizing `<saml2:LogoutRequest>` Authentication
-
-To customize validation, you can implement your own `Saml2LogoutRequestValidator`.
-At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutRequestValidator` like so:
-
-[source,java]
-----
-@Component
-public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
-	private final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();
-
-	@Override
-    public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {
-		 // verify signature, issuer, destination, and principal name
-		Saml2LogoutValidatorResult result = delegate.authenticate(authentication);
-
-		LogoutRequest logoutRequest = // ... parse using OpenSAML
-        // perform custom validation
-    }
-}
-----
-
-Then, you can supply your custom `Saml2LogoutRequestValidator` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)
-        )
-    );
-----
-
-=== Customizing `<saml2:LogoutResponse>` Authentication
-
-To customize validation, you can implement your own `Saml2LogoutResponseValidator`.
-At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutResponseValidator` like so:
-
-[source,java]
-----
-@Component
-public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
-	private final Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();
-
-	@Override
-    public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {
-		// verify signature, issuer, destination, and status
-		Saml2LogoutValidatorResult result = delegate.authenticate(parameters);
-
-		LogoutResponse logoutResponse = // ... parse using OpenSAML
-        // perform custom validation
-    }
-}
-----
-
-Then, you can supply your custom `Saml2LogoutResponseValidator` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutResponse((response) -> response
-            .logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
-        )
-    );
-----
-
-=== Customizing `<saml2:LogoutRequest>` storage
-
-When your application sends a `<saml2:LogoutRequest>`, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `<saml2:LogoutResponse>` can be verified.
-
-If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestRepository(myCustomLogoutRequestRepository)
-        )
-    );
-----

+ 277 - 0
docs/modules/ROOT/pages/servlet/saml2/logout.adoc

@@ -0,0 +1,277 @@
+[[servlet-saml2login-logout]]
+= Performing Single Logout
+
+Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout.
+
+Briefly, there are two use cases Spring Security supports:
+
+* **RP-Initiated** - Your application has an endpoint that, when POSTed to, will logout the user and send a `saml2:LogoutRequest` to the asserting party.
+Thereafter, the asserting party will send back a `saml2:LogoutResponse` and allow your application to respond
+* **AP-Initiated** - Your application has an endpoint that will receive a `saml2:LogoutRequest` from the asserting party.
+Your application will complete its logout at that point and then send a `saml2:LogoutResponse` to the asserting party.
+
+[NOTE]
+In the **AP-Initiated** scenario, any local redirection that your application would do post-logout is rendered moot.
+Once your application sends a `saml2:LogoutResponse`, it no longer has control of the browser.
+
+== Minimal Configuration for Single Logout
+
+To use Spring Security's SAML 2.0 Single Logout feature, you will need the following things:
+
+* First, the asserting party must support SAML 2.0 Single Logout
+* Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
+* Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
+
+You can begin from the initial minimal example and add the following configuration:
+
+[source,java]
+----
+@Value("${private.key}") RSAPrivateKey key;
+@Value("${public.certificate}") X509Certificate certificate;
+
+@Bean
+RelyingPartyRegistrationRepository registrations() {
+    Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
+    RelyingPartyRegistration registration = RelyingPartyRegistrations
+            .fromMetadataLocation("https://ap.example.org/metadata")
+            .registrationId("id")
+            .signingX509Credentials((signing) -> signing.add(credential)) <1>
+            .build();
+    return new InMemoryRelyingPartyRegistrationRepository(registration);
+}
+
+@Bean
+SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
+    http
+        .authorizeRequests((authorize) -> authorize
+            .anyRequest().authenticated()
+        )
+        .saml2Login(withDefaults())
+        .saml2Logout(withDefaults()); <2>
+
+    return http.build();
+}
+----
+<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to xref:servlet/saml2/login.adoc#servlet-saml2login-rpr-duplicated[multiple instances]
+<2> - Second, indicate that your application wants to use SAML SLO to logout the end user
+
+=== Runtime Expectations
+
+Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
+Your application will then do the following:
+
+1. Logout the user and invalidate the session
+2. Use a `Saml2LogoutRequestResolver` to create, sign, and serialize a `<saml2:LogoutRequest>` based on the xref:servlet/saml2/login.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the currently logged-in user.
+3. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
+4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
+5. Redirect to any configured successful logout endpoint
+
+Also, your application can participate in an AP-initiated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
+
+1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
+2. Logout the user and invalidate the session
+3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the xref:servlet/saml2/login.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the just logged-out user
+4. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
+
+== Configuring Logout Endpoints
+
+There are three behaviors that can be triggered by different endpoints:
+
+* RP-initiated logout, which allows an authenticated user to `POST` and trigger the logout process by sending the asserting party a `<saml2:LogoutRequest>`
+* AP-initiated logout, which allows an asserting party to send a `<saml2:LogoutRequest>` to the application
+* AP logout response, which allows an asserting party to send a `<saml2:LogoutResponse>` in response to the RP-initiated `<saml2:LogoutRequest>`
+
+The first is triggered by performing normal `POST /logout` when the principal is of type `Saml2AuthenticatedPrincipal`.
+
+The second is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLRequest` signed by the asserting party.
+
+The third is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLResponse` signed by the asserting party.
+
+Because the user is already logged in or the original Logout Request is known, the `registrationId` is already known.
+For this reason, `+{registrationId}+` is not part of these URLs by default.
+
+This URL is customizable in the DSL.
+
+For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to `GET /SLOService.saml2`.
+To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so:
+
+====
+.Java
+[source,java,role="primary"]
+----
+http
+    .saml2Logout((saml2) -> saml2
+        .logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
+        .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
+    );
+----
+====
+
+You should also configure these endpoints in your `RelyingPartyRegistration`.
+
+== Customizing `<saml2:LogoutRequest>` Resolution
+
+It's common to need to set other values in the `<saml2:LogoutRequest>` than the defaults that Spring Security provides.
+
+By default, Spring Security will issue a `<saml2:LogoutRequest>` and supply:
+
+* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation`
+* The `ID` attribute - a GUID
+* The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
+* The `<NameID>` element - from `Authentication#getName`
+
+To add other values, you can use delegation, like so:
+
+[source,java]
+----
+@Bean
+Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationResolver registrationResolver) {
+	OpenSaml4LogoutRequestResolver logoutRequestResolver
+			new OpenSaml4LogoutRequestResolver(registrationResolver);
+	logoutRequestResolver.setParametersConsumer((parameters) -> {
+		String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
+		String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
+		LogoutRequest logoutRequest = parameters.getLogoutRequest();
+		NameID nameId = logoutRequest.getNameID();
+		nameId.setValue(name);
+		nameId.setFormat(format);
+	});
+	return logoutRequestResolver;
+}
+----
+
+Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
+
+[source,java]
+----
+http
+    .saml2Logout((saml2) -> saml2
+        .logoutRequest((request) -> request
+            .logoutRequestResolver(this.logoutRequestResolver)
+        )
+    );
+----
+
+== Customizing `<saml2:LogoutResponse>` Resolution
+
+It's common to need to set other values in the `<saml2:LogoutResponse>` than the defaults that Spring Security provides.
+
+By default, Spring Security will issue a `<saml2:LogoutResponse>` and supply:
+
+* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation`
+* The `ID` attribute - a GUID
+* The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
+* The `<Status>` element - `SUCCESS`
+
+To add other values, you can use delegation, like so:
+
+[source,java]
+----
+@Bean
+public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationResolver registrationResolver) {
+	OpenSaml4LogoutResponseResolver logoutRequestResolver =
+			new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
+	logoutRequestResolver.setParametersConsumer((parameters) -> {
+		if (checkOtherPrevailingConditions(parameters.getRequest())) {
+			parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
+		}
+	});
+	return logoutRequestResolver;
+}
+----
+
+Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
+
+[source,java]
+----
+http
+    .saml2Logout((saml2) -> saml2
+        .logoutRequest((request) -> request
+            .logoutRequestResolver(this.logoutRequestResolver)
+        )
+    );
+----
+
+== Customizing `<saml2:LogoutRequest>` Authentication
+
+To customize validation, you can implement your own `Saml2LogoutRequestValidator`.
+At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutRequestValidator` like so:
+
+[source,java]
+----
+@Component
+public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
+	private final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();
+
+	@Override
+    public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {
+		 // verify signature, issuer, destination, and principal name
+		Saml2LogoutValidatorResult result = delegate.authenticate(authentication);
+
+		LogoutRequest logoutRequest = // ... parse using OpenSAML
+        // perform custom validation
+    }
+}
+----
+
+Then, you can supply your custom `Saml2LogoutRequestValidator` in the DSL as follows:
+
+[source,java]
+----
+http
+    .saml2Logout((saml2) -> saml2
+        .logoutRequest((request) -> request
+            .logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)
+        )
+    );
+----
+
+== Customizing `<saml2:LogoutResponse>` Authentication
+
+To customize validation, you can implement your own `Saml2LogoutResponseValidator`.
+At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutResponseValidator` like so:
+
+[source,java]
+----
+@Component
+public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
+	private final Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();
+
+	@Override
+    public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {
+		// verify signature, issuer, destination, and status
+		Saml2LogoutValidatorResult result = delegate.authenticate(parameters);
+
+		LogoutResponse logoutResponse = // ... parse using OpenSAML
+        // perform custom validation
+    }
+}
+----
+
+Then, you can supply your custom `Saml2LogoutResponseValidator` in the DSL as follows:
+
+[source,java]
+----
+http
+    .saml2Logout((saml2) -> saml2
+        .logoutResponse((response) -> response
+            .logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
+        )
+    );
+----
+
+== Customizing `<saml2:LogoutRequest>` storage
+
+When your application sends a `<saml2:LogoutRequest>`, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `<saml2:LogoutResponse>` can be verified.
+
+If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:
+
+[source,java]
+----
+http
+    .saml2Logout((saml2) -> saml2
+        .logoutRequest((request) -> request
+            .logoutRequestRepository(myCustomLogoutRequestRepository)
+        )
+    );
+----

+ 74 - 0
docs/modules/ROOT/pages/servlet/saml2/metadata.adoc

@@ -0,0 +1,74 @@
+[[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"]
+----
+DefaultRelyingPartyRegistrationResolver 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"))
+----
+====
+
+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"))
+----
+====