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 SAML 2 Profiles. Our implementation is currently limited to a simple authentication scheme. |
SAML 2 Service Provider, SP a.k.a. a relying party, support existed as an independent project since 2009. The 1.0.x branch is still in use, including in the 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 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 core Spring Security instead.
saml2Login()
is aimed to support a fraction of the 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
entityId = {baseUrl}/saml2/service-provider-metadata/{registrationId}
{baseUrl}/login/saml2/sso/{registrationId}
Converter<Assertion, Collection<? extends GrantedAuthority>>
GrantedAuthoritiesMapper
java.security.cert.X509Certificate
format.
AuthNRequest
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:
@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
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .saml2Login() .relyingPartyRegistrationRepository(...) ; } }
The RelyingPartyRegistration
object represents the mapping between this application, the SP, and the asserting party, the IDP.
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}
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
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
ENCRYPTION
key in the list.
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.
In the use case where an application uses multiple identity providers it becomes
obvious that some configuration is duplicated between two RelyingPartyRegistration
objects
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.
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
single signon URL - defaults to {baseUrl}/login/saml2/sso/{registrationId}
Other known configuration names that also use this same value
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.
public interface Saml2AuthenticationRequestFactory { String createAuthenticationRequest(Saml2AuthenticationRequest request); }
We are currently working with the Spring Boot team on the 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
Launch the Spring Boot application
./gradlew :spring-security-samples-boot-saml2login:bootRun
Open a browser
This will take you to an identity provider, log in using:
user
password
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.
spring: security: saml2: login: relying-parties: - entity-id: &idp-entity-id https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php registration-id: simplesamlphp web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.apps.pcfone.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}