瀏覽代碼

Restructure SAML 2.0 Documentation

Issue gh-8763
Josh Cummings 5 年之前
父節點
當前提交
e6d1e2cf81
共有 1 個文件被更改,包括 613 次插入319 次删除
  1. 613 319
      docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc

+ 613 - 319
docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc

@@ -1,293 +1,572 @@
-[[servlet-saml2-login]]
+[[servlet-saml2login]]
 == SAML 2.0 Login
+:figures: images/servlet/saml2
+:icondir: images/icons
 
-The SAML 2.0 Login, `saml2Login()`, feature provides an application with the capability to have users log in to the application by using their existing account at an SAML 2.0 Identity Provider (Okta, ADFS, etc).
+The SAML 2.0 Login feature provides an application with the capability to act as a SAML 2.0 Service Provider, having users log in to the application by using their existing account at a SAML 2.0 Identity Provider (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].
-Our implementation is currently limited to a simple authentication scheme.
 
 [[servlet-saml2-spring-security-history]]
-=== SAML 2 Support in Spring Security
-
-SAML 2 Service Provider, SP a.k.a. a relying party, support existed as an
-https://github.com/spring-projects/spring-security-saml/tree/1e013b07a7772defd6a26fcfae187c9bf661ee8f#spring-saml[independent project]
-since 2009. The 1.0.x branch is still in use, including in the
-https://github.com/cloudfoundry/uaa[Cloud Foundry User Account and Authentication Server] that
-also created a SAML 2.0 Identity Provider implementation based on the SP implementation.
-
-In 2018 we experimented with creating an updated implementation of both a
-https://github.com/spring-projects/spring-security-saml#spring-saml[Service Provider and Identity Provider]
-as a standalone library. After careful, and lengthy, deliberation we, the Spring Security team, decided
-to discontinue that effort. While this effort created a replacement for that standalone 1.0.x library
-we didn't feel that we should build a library on top of another library.
-
-Instead we opted to provide framework support for SAML 2 authentication as part of
-https://github.com/spring-projects/spring-security[core Spring Security] instead.
-
-[[servlet-saml2-login-concepts]]
-=== Saml 2 Login - High Level Concepts
-
-`saml2Login()` is aimed to support a fraction of the https://saml.xml.org/saml-specifications[SAML 2 feature set]
-with a focus on authentication being a Service Provider, SP, a relying party, receiving XML assertions from an
-Identity Provider, aka an asserting party.
-
-A SAML 2 login, or authentication, is the concept that the SP receives and validates an XML message called
-an assertion from an IDP.
-
-There are currently two supported authentication flows
-
-1. IDP Initiated flow - example: You login in directly to Okta, and then select a web application to be authenticated for.
-Okta, the IDP, sends an assertion to the web application, the SP.
-2. SP Initiated flow - example: You access a web application, a SP, the application sends an
-authentication request to the IDP requesting an assertion. Upon successful authentication on the IDP,
-the IDP sends an assertion to the SP.
-
-[[servlet-saml2-login-feature-set]]
-=== Saml 2 Login - Current Feature Set
-
-1. Service Provider (SP/Relying Party) is identified by `+entityId = {baseUrl}/saml2/service-provider-metadata/{registrationId}+`
-2. Receive assertion embedded in a SAML response via Http-POST or Http-Redirect at `+{baseUrl}/login/saml2/sso/{registrationId}+`
-3. Requires the assertion to be signed, unless the response is signed
-4. Supports encrypted assertions
-5. Supports encrypted NameId elements
-6. Allows for extraction of assertion attributes into authorities by using a `Converter<Assertion, Collection<? extends GrantedAuthority>>`
-7. Allows mapping and approved access listing for authorities by using a `GrantedAuthoritiesMapper`
-8. Public keys in `java.security.cert.X509Certificate` format.
-9. SP Initiated Authentication via an `AuthNRequest`
-
-[[servlet-saml2-login-tbd]]
-==== Saml 2 Login - Not Yet Supported
-
-1. Mappings assertion conditions and attributes to session features (timeout, tracking, etc)
-2. Single logout
-3. Receiving and validating standalone assertion (not wrapped in a response object)
-
-[[servlet-saml2-javaconfig]]
-=== Saml 2 Login - Introduction to Java Configuration
-
-To add `saml2Login()` to a Spring Security filter chain,
-the minimal Java configuration requires a configuration repository,
-the `RelyingPartyRegistrationRepository`, that contains the SAML configuration and
-the invocation of the `HttpSecurity.saml2Login()` method:
-[source,java]
+Since 2009, support for service providers 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 once started in 2017 for <<oauth2,Spring Security's OAuth 2.0 support>>.
+
+[NOTE]
+====
+A working sample for {gh-samples-url}/boot/saml2login[SAML 2.0 Login] is available in the {gh-samples-url}[Spring Security repository].
+====
+
+[[servlet-saml2login-minimaldependencies]]
+=== Minimal Dependencies
+
+SAML 2.0 service provider support is found 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 identity provider metadata.
+
+==== Specifying Identity Provider Metadata
+
+In a Spring Boot application, to specify an identity provider's metadata, simply do:
+
+[source,yml]
 ----
-@EnableWebSecurity
-public class SecurityConfig extends WebSecurityConfigurerAdapter {
+spring:
+  security:
+    saml2:
+      relyingparty:
+        registration:
+          example:
+            identityprovider:
+              entity-id: https://idp.example.com/issuer
+              verification.credentials:
+                - certificate-location: "classpath:idp.crt"
+              singlesignon.url: https://idp.example.com/issuer/SSO.saml2
+              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.saml2` is the endpoint where the identity provider is expecting `AuthnRequest` s.
+
+And that's it!
+
+From here, consider jumping to:
+
+* <<servlet-saml2login-architecture,How SAML 2.0 Login Works>>
+* <<servlet-saml2login-authenticatedprincipal,How to Use the `Saml2AuthenticatedPrincipal`>>
+* <<servlet-saml2login-sansboot,How to Configure without Spring Boot>>
+
+[[servlet-saml2login-architecture]]
+=== How SAML 2.0 Login Works
+
+When the above configuration is used, the application will automatically configure itself as a SAML 2.0 Service Provider - also called a relying party - that points to one or many identity providers - also called asserting parties.
+
+[NOTE]
+Identity Provider and Asserting Party are synonymous, as are Service Provider and Relying Party.
+
+There are two supported authentication flows:
+
+1. AP-Initiated flow, which is when you login in directly to the asserting party, and then select a web application to be authenticated for.
+2. RP-Initiated flow, which is when you access your relying party, and it sends an authentication request to the asserting party.
+You then authenticate and are redirected back to the relying party.
+
+To see this in action, you can navigate to a protected page in your app, for example `http://localhost:8080` is protected by default, and it will redirect you to the configured asserting party where you can authenticate.
+
+Once authenticated, the asserting party will issue an authentication response.
+Your application will then:
 
-    @Bean
-    public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
-        //SAML configuration
-        //Mapping this application to one or more Identity Providers
-        return new InMemoryRelyingPartyRegistrationRepository(...);
+1. Validate the response's signature against the set of public keys obtained from configuration
+2. Decrypt any encrypted assertions
+3. Validate the `ExpiresAt` and `NotBefore` timestamps, the `Issuer` url, the `<Subject>` and any `<AudienceRestriction>` conditions
+4. Map each attribute in the `AttributeStatement` into principal attributes
+5. Grant the authority `ROLE_USER` to the resulting authentication
+
+[TIP]
+Because Spring Security iterates through the set of configured public keys, it's possible to achieve key rotation by adding a new key to the list before removing a key you are retiring.
+
+The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Saml2AuthenticatedPrincipal` object, and `Authentication#getName` maps to the first assertion's `NameID` element, if one is present.
+
+[[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 { }
     }
+}
+----
+====
 
-    @Override
-    protected void configure(HttpSecurity http) throws Exception {
+If the application doesn't expose a `WebSecurityConfigurerAdapter` bean, then Spring Boot will expose the above default one.
+
+Replacing this is as simple as 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())
-        ;
+            .saml2Login(withDefaults());
     }
 }
 ----
 
-The bean declaration is a convenient, but optional, approach.
-You can directly wire up the repository using a method call
-[source,java]
+.Kotlin
+[source,kotlin,role="secondary"]
 ----
 @EnableWebSecurity
-public class SecurityConfig extends WebSecurityConfigurerAdapter {
+class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
+    override fun configure(http: HttpSecurity) {
+        http {
+            authorizeRequests {
+                authorize("/messages/**", hasAuthority("ROLE_USER"))
+                authorize(anyRequest, authenticated)
+            }
+            saml2Login {
+            }
+        }
+    }
+}
+----
+====
 
-    @Override
-    protected void configure(HttpSecurity http) throws Exception {
+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 `RelyingPartyRegistrationRepository`, which represents the AP and RP 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
+====
+[source,java]
+----
+@Value("${metadata.location}")
+String assertingPartyMetadataLocation;
+
+@Bean
+public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
+	RelyingPartyRegistration registration = RelyingPartyRegistrations
+            .fromMetadataLocation(assertingPartyMetadataLocation)
+            .registrationId("example")
+            .build();
+    return new InMemoryRelyingPartyRegistrationRepository(registration);
+}
+----
+====
+
+Or you can provide each detail manually, as you can see below:
+
+.Relying Party Registration Repository Manual Configuration
+====
+[source,java]
+----
+@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);
+}
+----
+====
+
+[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-configuration:
+
+.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(...)
-            )
-        ;
+                .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-saml2-relyingpartyregistration]]
-==== RelyingPartyRegistration
-The https://github.com/spring-projects/spring-security/blob/5.2.0.RELEASE/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java[`RelyingPartyRegistration`]
-object represents the mapping between this application, the SP, and the asserting party, the IDP.
+=== RelyingPartyRegistration
+A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`]
+instance represents a link between an RP and AP's metadata.
+
+In a `RelyingPartyRegistration`, you can provide RP 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 AP metadata like its `Issuer` value, where it expects AuthnRequests to be sent to, and any credentials that it owns for the purposes of the RP verifying or encrypting paylods.
+
+The following `RelyingPartyRegistration` is the minimum required for most setups:
+
+[source,java]
+----
+RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
+        .fromMetadataLocation("https://ap.example.org/metadata")
+        .registrationId("my-id")
+        .build();
+----
+
+Though a more sophisticated setup is also possible, like so:
+
+[source,java]
+----
+RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
+        .entityId("{baseUrl}/{registrationId}")
+        .decryptionX509Credentials(c -> c.add(relyingPartyDecryptingCredential()))
+        .assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
+        .assertingParty(party -> party
+                .entityId("https://ap.example.org")
+                .verificationX509Credentials(c -> c.add(assertingPartyVerifyingCredential()))
+                .singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
+        );
+----
+
+[TIP]
+The top-level metadata methods are details about the RP. The methods inside `assertingPartyDetails` are details about the AP.
+
+[NOTE]
+The location where an RP is expecting SAML Responses is known as the Assertion Consumer Service Location.
+
+The default for the RP's `entityId` is `+{baseUrl}/saml2/service-provider-metadata/{registrationId}+`.
+This is this value needed when configuring the AP to know about your RP.
+
+The default for the `assertionConsumerServiceLocation` is `+/login/saml2/sso/{registrationId}+`.
+It's mapped by default to {security-api-url}org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.html[`Saml2WebSsoAuthenticationFilter`] in the filter chain.
 
 [[servlet-saml2-rpr-uripatterns]]
-===== URI Patterns
-
-URI patterns are frequenty used to automatically generate URIs based on
-an incoming request. The URI patterns in `saml2Login` can contain the following variables
-
-* `baseUrl`
-* `registrationId`
-* `baseScheme`
-* `baseHost`
-* `basePort`
-
-For example:
-`+{baseUrl}/login/saml2/sso/{registrationId}+`
-
-[[servlet-saml2-rpr-relyingparty]]
-===== Relying Party
-
-* `registrationId` - (required) a unique identifer for this configuration mapping.
-This identifier may be used in URI paths, so care should be taken that no URI encoding is required.
-* `localEntityIdTemplate` - (optional) A URI pattern that creates an entity ID for this application based on the incoming request. The default is
-`+{baseUrl}/saml2/service-provider-metadata/{registrationId}+` and for a small sample application
-it would look like
-```
-http://localhost:8080/saml2/service-provider-metadata/my-test-configuration
-```
-There is no requirement that this configuration option is a pattern, it can be a fixed URI value.
-
-* `assertionConsumerServiceUrlTemplate` - (optional) A URI pattern that denotes the assertion
-consumer service URI to be sent with any `AuthNRequest` from the SP to the IDP during the SP initiated flow.
-While this can be a pattern the actual URI must resolve to the ACS endpoint on the SP.
-The default value is `+{baseUrl}/login/saml2/sso/{registrationId}+` and maps directly to the
-https://github.com/spring-projects/spring-security/blob/5.2.0.RELEASE/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java#L42[`Saml2WebSsoAuthenticationFilter`] endpoint
-* `credentials` - A list of credentials, private keys and x509 certificates, used for
-message signing, verification, encryption and decryption.
-This list can contain redundant credentials to allow for easy rotation of credentials.
-For example
-** [0] - X509Certificate{VERIFICATION,ENCRYPTION} - The IDP's first public key used for
-verification and encryption.
-** [1] - X509Certificate/{VERIFICATION,ENCRYPTION} - The IDP's second verification key used for verification.
-Encryption is always done using the first `ENCRYPTION` key in the list.
-** [2] - PrivateKey/X509Certificate{SIGNING,DECRYPTION} - The SP's first signing and decryption credential.
-** [3] - PrivateKey/X509Certificate{SIGNING,DECRYPTION} - The SP's second decryption credential.
-Signing is always done using the first `SIGNING` key in the list.
-* `ProviderDetails#entityId` - (required) the entity ID of the Identity Provider. Always a fixed URI value or string,
-no patterns allowed.
-* `ProviderDetails#webSsoUrl`  - (required) a fixed URI value for the IDP Single Sign On endpoint where
-the SP sends the `AuthNRequest` messages.
-* `ProviderDetails#signAuthNRequest` - A boolean indicating whether or not to sign the `AuthNRequest` with the SP's private key, defaults to `true`
-* `ProviderDetails#binding` - A `Saml2MessageBinding` indicating what kind of binding to use for the `AuthNRequest`, whether that be `REDIRECT` or `POST`, defaults to `REDIRECT`
-
-When an incoming message is received, signatures are always required, the system will first attempt
-to validate the signature using the certificate at index [0] and only move to the second
-credential if the first one fails.
-
-In a similar fashion, the SP configured private keys are used for decryption and attempted in the same order.
-The first SP credential (`type=SIGNING`) will be used when messages to the IDP are signed.
+==== URI Patterns
+
+You probably noticed in the above examples the `+{baseUrl}+` and `+{registrationId}+` placeholders.
+
+These are useful for generating URIs. As such, the RP'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/my-id+`
+
+The `entityId` above was defined as:
+
+`+{baseUrl}/{registrationId}+`
+
+which in a deployed application would translate to
+
+`+https://rp.example.org/my-id+`
+
+[[servlet-saml2-rpr-credentials]]
+==== Credentials
+
+You also likely noticed the credential that was used.
+
+Oftentimes, an RP 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 AP'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 `CertificateFactory` like so:
+
+[source,java]
+----
+Resource resource = new ClassPathResource("ap.crt");
+try (InputStream is = resource.getInputStream()) {
+	X509Certificate certificate = (X509Certificate)
+            CertificateFactory.getInstance("X.509").generateCertificate(is);
+	return Saml2X509Credential.verification(certificate);
+}
+----
+
+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:
+
+[source,java]
+----
+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);
+}
+----
+
+[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-saml2-rpr-duplicated]]
-===== Duplicated Relying Party Configurations
+==== Duplicated Relying Party Configurations
+
+When an application uses multiple asserting parties, some configuration is duplicated between `RelyingPartyRegistration` instances:
+
+* The RP's `entityId`
+* Its `assertionConsumerServiceLocation`, and
+* Its credentials, for example its signing or decryption credentials
 
-In the use case where an application uses multiple identity providers it becomes
-obvious that some configuration is duplicated between two `RelyingPartyRegistration` objects
+What's nice about this setup is credentials may be more easily rotated for some identity providers vs others.
 
-* localEntityIdTemplate
-* credentials (all SP credentials, IDP credentials change)
-* assertionConsumerServiceUrlTemplate
+The duplication can be alleviated in a few different ways.
 
-While there is some drawback in duplicating configuration values the back end
-configuration repository does not need to replicate this data storage model.
+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: ...
+----
 
-There is a benefit that comes with this setup. Credentials may be more easily rotated
-for some identity providers vs others. This object model can ensure that there is no
-disruption when configuration is changed in a multi IDP use case and you're not able to rotate
-credentials on all the identity providers.
+Second, in a database, it's not necessary to replicate `RelyingPartyRegistration`'s model.
 
-[[servlet-saml2-serviceprovider-metadata]]
-==== Service Provider Metadata
+Third, in Java, you can create a custom configuration method, like so:
 
-The Spring Security SAML 2 implementation does provide an endpoint for downloading
-SP metadata in XML format. The provider is mapped to: `+{baseUrl}/saml2/service-provider-metadata/{registrationId}+`
+[source,java]
+----
+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(
+            RelyingPartyRegistration
+                .fromMetadataLocation(oktaMetadataUrl)
+                .registrationId("okta")).build();
+
+    RelyingPartyRegistration azure = addRelyingPartyDetails(
+            RelyingPartyRegistration
+                .fromMetadataLocation(oktaMetadataUrl)
+                .registrationId("azure")).build();
+
+    return new InMemoryRelyingPartyRegistrationRepository(okta, azure);
+}
+----
 
 [[servlet-saml2-sp-initiated]]
-==== Authentication Requests - SP Initiated Flow
+=== RP-initiated Login
 
-To initiate an authentication from the web application, you can redirect to:
+You can initiate login by navigating to the SAML 2.0 login endpoint for a given `RelyingPartyRegistration`, like so:
 
 `+{baseUrl}/saml2/authenticate/{registrationId}+`
 
-This endpoint will generate an `AuthNRequest` either as a Redirect or POST depending on your `RelyingPartyRegistration`.
+Where you replace `+{baseUrl}+` and `+{registrationId}+` with the deployed location of your application and the identifier for that registration.
 
-[[servlet-saml2-sp-initiated-factory]]
-==== Customizing the AuthNRequest
+For example, if you were deployed to `https://rp.example.org` and you gave your registration an ID of `ping`, you could navigate to:
 
-To adjust the `AuthNRequest`, you can publish an instance of `Saml2AuthenticationRequestFactory`.
+`https://rp.example.org/saml2/authenticate/ping`
 
-For example, if you wanted to configure the `AuthNRequest` to request the IDP to send the SAML `Assertion` by REDIRECT, you could do:
+You can modify this by post-processing the `Saml2WebSsoAuthenticationRequestFilter`.
+
+By default, this filter will use HTTP-Redirect for the AuthNRequest.
+However, by setting `RelyingPartyRegistration.AssertingPartyDetails#singleSignOnServiceBinding` to `Saml2MessageType.POST`, like so, it will use HTTP-POST instead:
 
 [source,java]
 ----
-@Bean
-public Saml2AuthenticationRequestFactory authenticationRequestFactory() {
-    OpenSamlAuthenticationRequestFactory authenticationRequestFactory =
-        new OpenSamlAuthenticationRequestFactory();
-    authenticationRequestFactory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
-    return authenticationRequestFactory;
-}
+RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
+        // ...
+        .assertingPartyDetails(party -> party
+            // ...
+            .singleSignOnServiceBinding(Saml2MessageType.POST)
+        );
 ----
 
-[[servlet-saml2-sp-initiated-factory-delegate]]
-==== Delegating to an AuthenticationRequestFactory
+[[servlet-saml2-sp-initiated-factory]]
+==== Customizing the AuthNRequest
+
+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.
 
-Or, in circumstances where you need more control over what is sent as parameters to the `AuthenticationRequestFactory`, you can use delegation:
+If you don't need information from the `HttpServletRequest` to make your decision, then the easiest way is to 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 it yourself, like so:
 
 [source,java]
 ----
 @Component
-public class IssuerSaml2AuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
-	private OpenSamlAuthenticationRequestFactory delegate = new OpenSamlAuthenticationRequestFactory();
+public class AuthnRequestConverter implements
+        Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
 
-	@Override
-	public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
-		return this.delegate.createAuthenticationRequest(request);
-	}
+	private final AuthnRequestBuilder authnRequestBuilder;
+	private final IssuerBuilder issuerBuilder;
 
-	@Override
-    public Saml2PostAuthenticationRequest createPostAuthenticationRequest
-        (Saml2AuthenticationRequestContext context) {
+	// ... constructor
 
-		String issuer = // ... calculate issuer
+	public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
+		MySaml2AuthenticationRequestContext custom = (MySaml2AuthenticationRequestContext) context;
+		Issuer issuer = issuerBuilder.buildObject();
+		issuer.setValue(context.getIssuer());
 
-		Saml2AuthenticationRequestContext customIssuer = Saml2AuthenticationRequestContext.builder()
-                .assertionConsumerServiceUrl(context.getAssertionConsumerServiceUrl())
-                .issuer(issuer)
-                .relayState(context.getRelayState())
-                .relyingPartyRegistration(context.getRelyingPartyRegistration())
-                .build();
+		AuthnRequest authnRequest = authnRequestBuilder.buildObject();
+		authnRequest.setIssuer(iss);
+        authnRequest.setDestination(context.getDestination());
+		authnRequest.setAssertionConsumerServiceURL(context.getAssertionConsumerServiceUrl());
 
-		return this.delegate.createPostAuthenticationRequest(customIssuer);
+		// ... additional settings
+
+		authRequest.setForceAuthn(custom.getForceAuthn());
+		return authnRequest;
 	}
+}
+----
 
-	@Override
-    public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest
-        (Saml2AuthenticationRequestContext context) {
+And then you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory`, and publish them as `@Bean` s:
 
-		throw new UnsupportedOperationException("unsupported");
-	}
+[source,java]
+----
+@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) {
+
+	OpenSamlAuthenticationRequestFactory authenticationRequestFactory =
+            new OpenSamlAuthenticationRequestFactory();
+	authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
+	return authenticationRequestFactory;
 }
 ----
 
 [[servlet-saml2-login-customize]]
 === Customizing Authentication Logic
 
-By default Spring Security configures the `OpenSamlAuthenticationProvider`
-to validate and parse the SAML 2 response and assertions that are received.
-This provider has three configuration options
+To verify SAML 2.0 Responses, Spring Security uses `OpenSamlAuthenticationProvider` by default.
 
-1. An authorities extractor - extract group information from the assertion
-2. An authorities mapper - map extracted group information to internal authorities
-3. Response time validation duration - the built in tolerances for timestamp validation
-should be used when there may be a time synchronization issue.
+You can configure this in a number of ways including:
 
-One customization strategy is to use an `ObjectPostProcessor`, which allows you to modify the
-objects created by the implementation. Another option is to override the authentication
-manager for the filter that intercepts the SAMLResponse.
+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
 
-[[servlet-saml2-opensamlauthenticationprovider]]
-==== OpenSamlAuthenticationProvider ObjectPostProcessor
+To configure these, you'll use the `saml2Login#authenticationManager` method in the DSL.
+
+[[servlet-saml2-opensamlauthenticationprovider-authenticationmanager]]
+==== 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 `OpenSamlAuthenticationProvider` 's default assertion validator with some tolerance:
 
 [source,java]
 ----
@@ -296,62 +575,113 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Override
     protected void configure(HttpSecurity http) throws Exception {
-        ObjectPostProcessor<OpenSamlAuthenticationProvider> processor = new ObjectPostProcessor<>() {
-            @Override
-            public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
-                provider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
-                provider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
-                provider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
-                return provider;
-            }
-        };
+        OpenSamlAuthenticationProvider authenticationProvider = new OpenSamlAuthenticationProvider();
+        authenticationProvider.setAssertionValidator(OpenSamlAuthenticationProvider
+                .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(authorize -> authorize
+            .authorizeRequests(authz -> authz
                 .anyRequest().authenticated()
             )
             .saml2Login(saml2 -> saml2
-               .addObjectPostProcessor(processor)
-            )
-        ;
+                .authenticationManager(new ProviderManager(authenticationProvider))
+            );
     }
 }
 ----
 
-[[servlet-saml2-opensamlauthenticationprovider-authenticationmanager]]
-==== Configure OpenSamlAuthenticationProvider as an Authentication Manager
-We can leverage the same method, `authenticationManager`, to override and customize the default
-`OpenSamlAuthenticationProvider`.
+==== 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:
 
 [source,java]
 ----
 @EnableWebSecurity
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    @Autowired
+    UserDetailsService userDetailsService;
 
     @Override
     protected void configure(HttpSecurity http) throws Exception {
-        OpenSamlAuthenticationProvider authProvider = new OpenSamlAuthenticationProvider();
-        authProvider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
-        authProvider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
-        authProvider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
+        OpenSamlAuthenticationProvider authenticationProvider = new OpenSamlAuthenticationProvider();
+        authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
+        	Saml2Authentication authentication = OpenSamlAuthenticationProvider
+                    .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(authorize -> authorize
+            .authorizeRequests(authz -> authz
                 .anyRequest().authenticated()
             )
             .saml2Login(saml2 -> saml2
-                .authenticationManager(new ProviderManager(asList(authProvider)))
-            )
-        ;
+                .authenticationManager(new ProviderManager(authenticationProvider))
+            );
     }
 }
 ----
+<1> First, call the default converter, which extracts attributes and authorities from the response
+<2> Second, call the `UserDetailsService` using the relevant information
+<3> Third, return a custom authentication that includes the user details
+
+[NOTE]
+It's not required to call `OpenSamlAuthenticationProvider` 's default authentication converter.
+It returns a `Saml2AuthenticatedPrincipal` containing the attributes it extracted from `AttributeStatement` s as well as the single `ROLE_USER` authority.
+
+==== Performing additional validation
+
+`OpenSamlAuthenticationProvider` 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 `OpenSamlAuthenticationProvider` 's default and then performs its own.
+
+For example, you can use OpenSAML's `OneTimeUseConditionValidator` to also validate a `<OneTimeUse>` condition, like so:
+
+[source,java]
+----
+OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+OneTimeUseConditionValidator validator = ...;
+provider.setAssertionValidator(assertionToken -> {
+    Saml2ResponseValidatorResult result = OpenSamlAuthenticationProvider
+            .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.contact(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage()));
+});
+----
+
+[NOTE]
+While recommended, it's not necessary to call `OpenSamlAuthenticationProvider` '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-saml2-custom-authenticationmanager]]
-==== Custom Authentication Manager
-The authentication manager for the security filter can also be overwritten, using your own
-custom `AuthenticationManager` implementation.
-This authentication manager should expect a `Saml2AuthenticationToken` object
-containing the SAML 2 Response XML data.
+==== Using a Custom Authentication Manager
+
+Of course, the `authenticationManager` DSL method can be used to perform a completely custom SAML 2.0 authentication.
+This authentication manager should expect a `Saml2AuthenticationToken` object containing the SAML 2 Response XML data.
 
 [source,java]
 ----
@@ -373,90 +703,54 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 }
 ----
 
-[[servlet-saml2-sample-boot]]
-=== Spring Boot 2.x Sample
-
-We are currently working with the Spring Boot team on the
-https://github.com/spring-projects/spring-boot/issues/18260[Auto Configuration for Spring Security SAML Login].
-In the meantime, we have provided a Spring Boot sample that supports a Yaml configuration.
+[[servlet-saml2login-authenticatedprincipal]]
+=== Using `Saml2AuthenticatedPrincipal`
 
-To run the sample, follow these three steps
+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`.
 
-1. Launch the Spring Boot application
-** `./gradlew :spring-security-samples-boot-saml2login:bootRun`
-2. Open a browser
-** http://localhost:8080/[http://localhost:8080/]
-3. This will take you to an identity provider, log in using:
-** User: `user`
-** Password: `password`
+This means that you can access the principal in your controller like so:
 
-[[servlet-saml2-sample-idps]]
-==== Multiple Identity Provider Sample
+[source,java]
+----
+@Controller
+public class MainController {
+	@GetMapping("/")
+	public String index(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) {
+		String email = principal.getFirstAttribute("email");
+		model.setAttribute("email", email);
+		return "index";
+	}
+}
+----
 
-It's very simple to use multiple providers, but there are some defaults that
-may trip you up if you don't pay attention. In our SAML configuration of
-`RelyingPartyRegistration` objects, we default an SP entity ID to
-`+{baseUrl}/saml2/service-provider-metadata/{registrationId}+`
+[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.
 
-That means in our two provider configuration, our system would look like
+[[servlet-saml2login-metadata]]
+=== Publishing a Metadata Endpoint
 
-```
-registration-1 (Identity Provider 1) - Our local SP Entity ID is:
-http://localhost:8080/saml2/service-provider-metadata/registration-1
+You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
 
-registration-2 (Identity Provider 2) - Our local SP Entity ID is:
-http://localhost:8080/saml2/service-provider-metadata/registration-2
-```
+[source,java]
+----
+Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver =
+        new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository);
+Saml2MetadataFilter filter = new Saml2MetadataFilter(
+		relyingPartyRegistrationResolver,
+        new OpenSamlMetadataResolver());
+
+http
+    // ...
+    .saml2Login(withDefaults())
+    .addFilterBefore(new Saml2MetadataFilter(r), Saml2WebSsoAuthenticationFilter.class);
+----
 
-In this configuration, illustrated in the sample below, to the outside world,
-we have actually created two virtual Service Provider identities
-hosted within the same application.
+By default, the metadata endpoint is `+/saml2/service-provider-metadata/{registrationId}+`.
+You can change this by calling the `setRequestMatcher` method on the filter:
 
-[source,yaml]
+[source,java]
+----
+filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET"));
 ----
-spring:
-  security:
-    saml2:
-      login:
-        relying-parties:
-          - entity-id: &idp-entity-id https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php
-            registration-id: simplesamlphp
-            web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php
-            signing-credentials: &service-provider-credentials
-              - private-key: |
-                  -----BEGIN PRIVATE KEY-----
-                  MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE
-                  ...................SHORTENED FOR READ ABILITY...................
-                  INrtuLp4YHbgk1mi
-                  -----END PRIVATE KEY-----
-                certificate: |
-                  -----BEGIN CERTIFICATE-----
-                  MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC
-                  ...................SHORTENED FOR READ ABILITY...................
-                  RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B
-                  -----END CERTIFICATE-----
-            verification-credentials: &idp-certificates
-              - |
-                -----BEGIN CERTIFICATE-----
-                MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD
-                ...................SHORTENED FOR READ ABILITY...................
-                lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk
-                -----END CERTIFICATE-----
-          - entity-id: *idp-entity-id
-            registration-id: simplesamlphp2
-            web-sso-url: *idp-sso-url
-            signing-credentials: *service-provider-credentials
-            verification-credentials: *idp-certificates
-----
-
-If this is not desirable, you can manually override the local SP entity ID by using the
-
-[source,attrs="-attributes"]
-----
-localEntityIdTemplate = {baseUrl}/saml2/service-provider-metadata
-----
-
-If we change our local SP entity ID to this value, it is still important that we give
-out the correct single sign on URL (the assertion consumer service URL)
-for each registered identity provider based on the registration Id.
-`+{baseUrl}/login/saml2/sso/{registrationId}+`