Restructure SAML 2.0 Documentation

- Add images
- Standardize terminology
- Add detail about working with OpenSAML
- Reorganize sections

Closes gh-8763
This commit is contained in:
Josh Cummings 2020-08-28 12:42:44 -06:00
parent a9fe2cb377
commit 0a4766f21e
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
8 changed files with 331 additions and 93 deletions

View File

@ -40,7 +40,7 @@ Here's what you'll see in this release:
** Added https://github.com/spring-projects/spring-security/issues/5185[XML support]
** Improved https://github.com/spring-projects/spring-security/pull/7826[bearer token error handling] for JWT and Opaque Token
* SAML 2.0
** Added <<servlet-saml2-opensamlauthenticationprovider-authenticationmanager,AuthenticationManager>> configuration
** Added <<servlet-saml2login-opensamlauthenticationprovider-authenticationmanager,AuthenticationManager>> configuration
** Added support for https://github.com/spring-projects/spring-security/issues/7711[AuthNRequest signatures]
** Added support for https://github.com/spring-projects/spring-security/pull/7759[AuthNRequest POST binding]

View File

@ -3,32 +3,83 @@
:figures: images/servlet/saml2
:icondir: images/icons
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).
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-saml2-spring-security-history]]
Since 2009, support for service providers has existed as an https://github.com/spring-projects/spring-security-saml/tree/1e013b07a7772defd6a26fcfae187c9bf661ee8f#spring-saml[extension project].
[[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 once started in 2017 for <<oauth2,Spring Security's OAuth 2.0 support>>.
This process is similar to the one 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].
====
Let's take a look at how SAML 2.0 Relying Party Authentication works within Spring Security.
First, we see that, like <<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 <<servlet-securityfilterchain,`SecurityFilterChain`>> and <<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 <<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 <<servlet-exceptiontranslationfilter,`ExceptionTranslationFilter`>> initiates __Start Authentication__.
The configured <<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 <<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 <<servlet-authentication-providermanager,`AuthenticationManager`>>.
By default, it will use the <<servlet-saml2login-architecture,`OpenSamlAuthenticationProvider`>>.
image:{icondir}/number_3.png[] If authentication fails, then __Failure__
* The <<servlet-authentication-securitycontextholder, `SecurityContextHolder`>> is cleared out.
* The <<servlet-authentication-authenticationentrypoint,`AuthenticationEntryPoint`>> is invoked to restart the authentication process.
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
* The <<servlet-authentication-authentication, `Authentication`>> is set on the <<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 is found in `spring-security-saml2-service-provider`.
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 identity provider metadata.
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
@ -41,12 +92,12 @@ spring:
saml2:
relyingparty:
registration:
example:
adfs:
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.url: https://idp.example.com/issuer/sso
singlesignon.sign-request: false
----
@ -54,45 +105,132 @@ 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.
* `https://idp.example.com/issuer/sso` 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.
These are frequently abbreviated as AP and RP, respectively.
There are two supported authentication flows:
==== Runtime Expectations
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.
As configured above, the application processes any `+POST /login/saml2/sso/{registrationId}+` request containing a `SAMLResponse` parameter:
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.
[source,html]
----
POST /login/saml2/sso/adfs HTTP/1.1
Once authenticated, the asserting party will issue an authentication response.
Your application will then:
SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...
----
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
There are two ways to see induce your asserting party to generate a `SAMLResponse`:
[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.
* 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`.
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.
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 `OpenSamlAuthenticationRequestFactory` and `OpenSamlAuthenticationProvider` 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 `OpenSamlAuthenticationProvider`.
.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 <<servlet-authentication-providermanager,`AuthenticationManager`>>.
image:{icondir}/number_2.png[] The <<servlet-authentication-providermanager,`AuthenticationManager`>> invokes the `OpenSamlAuthenticationProvider`.
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[] 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_5.png[] Then, the provider decrypts any encrypted assertions.
If any decryptions fail, 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.
It is required that either the response or the assertions have signatures.
image:{icondir}/number_7.png[] Then, 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_8.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_9.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:
[source,java]
----
static {
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, in the event that you always want to always force the asserting party to reauthenticate the user, you can register your own `AuthnRequestMarshaller`, like so:
[source,java]
----
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, element);
}
private void configureAuthnRequest(AuthnRequest authnRequest) {
authnRequest.setForceAuthN(true);
}
}
});
}
----
The `requireInitialize` method may only be called once per application instance.
[[servlet-saml2login-sansboot]]
=== Overriding or Replacing Boot Auto Configuration
@ -132,7 +270,7 @@ fun configure(http: HttpSecurity) {
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:
You can replace this by exposing the bean within the application:
.Custom SAML 2.0 Login Configuration
====
@ -174,7 +312,7 @@ class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
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.
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.
@ -230,7 +368,7 @@ Note that `X509Support` is an OpenSAML class, used here in the snippet for brevi
[[servlet-saml2login-relyingpartyregistrationrepository-dsl]]
Alternatively, you can directly wire up the repository using the DSL, which will also override the auto-configuration:
Alternatively, you can directly wire up the repository using the DSL, which will also override the auto-configured `WebSecurityConfigurerAdapter`:
.Custom Relying Party Registration DSL
====
@ -275,14 +413,14 @@ class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
[NOTE]
A relying party can be multi-tenant by registering more than one relying party in the `RelyingPartyRegistrationRepository`.
[[servlet-saml2-relyingpartyregistration]]
[[servlet-saml2login-relyingpartyregistration]]
=== 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.
instance represents a link between an relying party and assering party'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.
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 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.
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:
@ -310,23 +448,24 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
----
[TIP]
The top-level metadata methods are details about the RP. The methods inside `assertingPartyDetails` are details about the AP.
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 an RP is expecting SAML Responses is known as the Assertion Consumer Service Location.
The location where a relying party is expecting SAML Responses is 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 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 {security-api-url}org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.html[`Saml2WebSsoAuthenticationFilter`] in the filter chain.
It's mapped by default to <<servlet-saml2login-authentication-saml2webssoauthenticationfilter,`Saml2WebSsoAuthenticationFilter`>> in the filter chain.
[[servlet-saml2-rpr-uripatterns]]
[[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 RP's `entityId` and `assertionConsumerServiceLocation` support the following 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
@ -340,7 +479,7 @@ For example, the `assertionConsumerServiceLocation` defined above was:
which in a deployed application would translate to
`+/my-login-endpoint/my-id+`
`+/my-login-endpoint/adfs+`
The `entityId` above was defined as:
@ -348,21 +487,22 @@ The `entityId` above was defined as:
which in a deployed application would translate to
`+https://rp.example.org/my-id+`
`+https://rp.example.com/adfs+`
[[servlet-saml2-rpr-credentials]]
[[servlet-saml2login-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.
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 AP's signed responses can be verified.
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 `CertificateFactory` like so:
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:
[source,java]
----
@ -374,8 +514,8 @@ try (InputStream is = resource.getInputStream()) {
}
----
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.
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:
@ -393,12 +533,44 @@ try (InputStream is = resource.getInputStream()) {
[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]]
[[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:
[source,java]
----
public class SingleRelyingPartyRegistrationResolver
implements Converter<HttpServletRequest, RelyingPartyRegistration> {
@Override
public RelyingPartyRegistration convert(HttpServletRequest request) {
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 RP's `entityId`
* The relying party's `entityId`
* Its `assertionConsumerServiceLocation`, and
* Its credentials, for example its signing or decryption credentials
@ -426,7 +598,7 @@ spring:
entity-id: ...
----
Second, in a database, it's not necessary to replicate `RelyingPartyRegistration`'s model.
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:
@ -456,27 +628,65 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
}
----
[[servlet-saml2-sp-initiated]]
=== RP-initiated Login
[[servlet-saml2login-sp-initiated-factory]]
=== Producing `<saml2:AuthnRequest>` s
You can initiate login by navigating to the SAML 2.0 login endpoint for a given `RelyingPartyRegistration`, like so:
As stated earlier, Spring Security's SAML 2.0 support produces a `<saml2:AuthnRequest>` to commence authentication with the asserting party.
`+{baseUrl}/saml2/authenticate/{registrationId}+`
Spring Security achieves this in part by registering the `Saml2WebSsoAuthenticationRequestFilter` in the filter chain.
This filter by default responds to endpoint `+/saml2/authenticate/{registrationId}+`.
Where you replace `+{baseUrl}+` and `+{registrationId}+` with the deployed location of your application and the identifier for that registration.
For example, if you were deployed to `https://rp.example.org` and you gave your registration an ID of `ping`, you could navigate to:
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`
You can modify this by post-processing the `Saml2WebSsoAuthenticationRequestFilter`.
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
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:
[[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)
);
----
====
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-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:
[source,java]
----
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails(party -> party
// ...
@ -484,16 +694,16 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
);
----
[[servlet-saml2-sp-initiated-factory]]
==== Customizing the AuthNRequest
[[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.
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 register a custom `AuthnRequestMarshaller` with OpenSAML.
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 it yourself, like so:
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:
[source,java]
----
@ -507,24 +717,24 @@ public class AuthnRequestConverter implements
// ... constructor
public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
MySaml2AuthenticationRequestContext custom = (MySaml2AuthenticationRequestContext) context;
MySaml2AuthenticationRequestContext myContext = (MySaml2AuthenticationRequestContext) context;
Issuer issuer = issuerBuilder.buildObject();
issuer.setValue(context.getIssuer());
issuer.setValue(myContext.getIssuer());
AuthnRequest authnRequest = authnRequestBuilder.buildObject();
authnRequest.setIssuer(iss);
authnRequest.setDestination(context.getDestination());
authnRequest.setAssertionConsumerServiceURL(context.getAssertionConsumerServiceUrl());
authnRequest.setDestination(myContext.getDestination());
authnRequest.setAssertionConsumerServiceURL(myContext.getAssertionConsumerServiceUrl());
// ... additional settings
authRequest.setForceAuthn(custom.getForceAuthn());
authRequest.setForceAuthn(myContext.getForceAuthn());
return authnRequest;
}
}
----
And then you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory`, and publish them as `@Bean` s:
Then, you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and publish them as `@Bean` s:
[source,java]
----
@ -549,10 +759,10 @@ Saml2AuthenticationRequestFactory authenticationRequestFactory(
}
----
[[servlet-saml2-login-customize]]
=== Customizing Authentication Logic
[[servlet-saml2login-authenticate-responses]]
=== Authenticating `<saml2:Response>` s
To verify SAML 2.0 Responses, Spring Security uses `OpenSamlAuthenticationProvider` by default.
To verify SAML 2.0 Responses, Spring Security uses <<servlet-saml2login-architecture,`OpenSamlAuthenticationProvider`>> by default.
You can configure this in a number of ways including:
@ -562,7 +772,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-saml2-opensamlauthenticationprovider-authenticationmanager]]
[[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.
@ -596,6 +806,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
----
[[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]]
==== Coordinating with a `UserDetailsService`
Or, perhaps you would like to include user details from a legacy `UserDetailsService`.
@ -632,14 +843,15 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
----
<1> First, call the default converter, which extracts attributes and authorities from the response
<2> Second, call the `UserDetailsService` using the relevant information
<2> Second, call the <<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 `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
[[servlet-saml2login-opensamlauthenticationprovider-additionalvalidation]]
==== Performing Additional Validation
`OpenSamlAuthenticationProvider` performs minimal validation on SAML 2.0 Assertions.
After verifying the signature, it will:
@ -649,6 +861,7 @@ After verifying the signature, it will:
To perform additional validation, you can configure your own assertion validator that delegates to `OpenSamlAuthenticationProvider` '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:
[source,java]
@ -677,11 +890,12 @@ provider.setAssertionValidator(assertionToken -> {
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]]
[[servlet-saml2login-authenticationmanager-custom]]
==== 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.
[[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.
[source,java]
----
@ -729,7 +943,7 @@ Because the SAML 2.0 specification allows for each attribute to have multiple va
`getFirstAttribute` is quite handy when you know that there is only one value.
[[servlet-saml2login-metadata]]
=== Publishing a Metadata Endpoint
=== Producing `<saml2:SPSSODescriptor>` Metadata
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
@ -747,6 +961,9 @@ http
.addFilterBefore(new Saml2MetadataFilter(r), Saml2WebSsoAuthenticationFilter.class);
----
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:
@ -754,3 +971,24 @@ You can change this by calling the `setRequestMatcher` method on the filter:
----
filter.setRequestMatcher(new 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`:
[source,java]
----
http
// ...
.logout(logout -> 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB