Separate SAML Docs

Issue gh-10367
This commit is contained in:
Josh Cummings 2021-10-29 11:29:35 -06:00
parent caad3d57e2
commit f39d272a86
5 changed files with 384 additions and 1996 deletions

View File

@ -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[]

File diff suppressed because it is too large Load Diff

View File

@ -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)
)
);
----

View File

@ -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)
)
);
----

View File

@ -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"))
----
====