Browse Source
Fixes gh-7472 https://github.com/spring-projects/spring-security/issues/7472pull/7506/head
3 changed files with 317 additions and 0 deletions
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
= SAML2 |
||||
|
||||
include::saml2-login.adoc[] |
||||
@ -0,0 +1,312 @@
@@ -0,0 +1,312 @@
|
||||
[[saml2login]] |
||||
== SAML 2.0 Login |
||||
|
||||
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). |
||||
|
||||
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. |
||||
|
||||
[[saml2login-spring-security-saml2-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. |
||||
|
||||
[[samllogin-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. |
||||
|
||||
[[samllogin-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 using a `Converter<Assertion, Collection<? extends GrantedAuthority>>` |
||||
7. Allows mapping and white listing of authorities using a `GrantedAuthoritiesMapper` |
||||
8. Public keys in `java.security.cert.X509Certificate` format. |
||||
9. SP Initiated Authentication via an `AuthNRequest` |
||||
|
||||
==== Saml 2 Login - Not Yet Supported |
||||
|
||||
1. Mappings assertion conditions and attributes to session features (timeout, tracking, etc) |
||||
2. Single logout |
||||
3. Dynamic metadata generation |
||||
4. Receiving and validating standalone assertion (not wrapped in a response object) |
||||
|
||||
[[samllogin-introduction-java-config]] |
||||
=== 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] |
||||
---- |
||||
@EnableWebSecurity |
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter { |
||||
|
||||
@Bean |
||||
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { |
||||
//SAML configuration |
||||
//Mapping this application to one or more Identity Providers |
||||
return new InMemoryRelyingPartyRegistrationRepository(...); |
||||
} |
||||
|
||||
@Override |
||||
protected void configure(HttpSecurity http) throws Exception { |
||||
http |
||||
.authorizeRequests() |
||||
.anyRequest().authenticated() |
||||
.and() |
||||
.saml2Login() |
||||
; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
The bean declaration is a convenient, but optional, approach. |
||||
You can directly wire up the repository using a method call |
||||
[source,java] |
||||
---- |
||||
@EnableWebSecurity |
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter { |
||||
|
||||
@Override |
||||
protected void configure(HttpSecurity http) throws Exception { |
||||
http |
||||
.authorizeRequests() |
||||
.anyRequest().authenticated() |
||||
.and() |
||||
.saml2Login() |
||||
.relyingPartyRegistrationRepository(...) |
||||
; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
==== 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. |
||||
|
||||
===== 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} |
||||
``` |
||||
|
||||
===== 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. |
||||
|
||||
* `remoteIdpEntityId` - (required) the entity ID of the Identity Provider. Always a fixed URI value or string, |
||||
no patterns allowed. |
||||
* `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 |
||||
* `idpWebSsoUrl` - (required) a fixed URI value for the IDP Single Sign On endpoint where |
||||
the SP sends the `AuthNRequest` messages. |
||||
* `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. |
||||
|
||||
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. |
||||
|
||||
===== Duplicated Relying Party Configurations |
||||
|
||||
In the use case where an application uses multiple identity providers it becomes |
||||
obvious that some configuration is duplicated between two `RelyingPartyRegistration` objects |
||||
|
||||
* localEntityIdTemplate |
||||
* credentials (all SP credentials, IDP credentials change) |
||||
* assertionConsumerServiceUrlTemplate |
||||
|
||||
While there is some drawback in duplicating configuration values the back end |
||||
configuration repository does not need to replicate this data storage model. |
||||
|
||||
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. |
||||
|
||||
==== Service Provider Metadata |
||||
|
||||
The Spring Security SAML 2 implementation does not yet provide an endpoint for downloading |
||||
SP metadata in XML format. The minimal pieces that are exchanged |
||||
|
||||
* *entity ID* - defaults to `{baseUrl}/saml2/service-provider-metadata/{registrationId}` |
||||
Other known configuration names that also use this same value |
||||
** Audience Restriction |
||||
* *single signon URL* - defaults to `{baseUrl}/login/saml2/sso/{registrationId}` |
||||
Other known configuration names that also use this same value |
||||
** Recipient URL |
||||
** Destination URL |
||||
** Assertion Consumer Service URL |
||||
* X509Certificate - the certificate that you configure as part of your {SIGNING,DECRYPTION} |
||||
credentials must be shared with the Identity Provider |
||||
|
||||
==== Authentication Requests - SP Initiated Flow |
||||
|
||||
To initiate an authentication from the web application, a simple redirect to |
||||
``` |
||||
{baseUrl}/saml2/authenticate/{registrationId} |
||||
``` |
||||
The endpoint will generate an `AuthNRequest` by invoking the `createAuthenticationRequest` method on a |
||||
configurable factory. Just expose the `Saml2AuthenticationRequestFactory` as a bean in your configuration. |
||||
[source,java] |
||||
---- |
||||
public interface Saml2AuthenticationRequestFactory { |
||||
String createAuthenticationRequest(Saml2AuthenticationRequest request); |
||||
} |
||||
---- |
||||
|
||||
[[samllogin-sample-boot]] |
||||
=== Spring Boot 2.x Sample |
||||
|
||||
We are currently working with the 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. |
||||
|
||||
To run the sample, follow these three steps |
||||
|
||||
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` |
||||
|
||||
==== Multiple Identity Provider Sample |
||||
|
||||
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} |
||||
``` |
||||
|
||||
That means in our two provider configuration, our system would look like |
||||
|
||||
``` |
||||
registration-1 (Identity Provider 1) - Our local SP Entity ID is: |
||||
http://localhost:8080/saml2/service-provider-metadata/registration-1 |
||||
|
||||
registration-2 (Identity Provider 2) - Our local SP Entity ID is: |
||||
http://localhost:8080/saml2/service-provider-metadata/registration-2 |
||||
``` |
||||
|
||||
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. |
||||
|
||||
[source,yaml] |
||||
---- |
||||
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 |
||||
``` |
||||
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}` |
||||
|
||||
|
||||
Loading…
Reference in new issue