Skip to content

Commit 8bc3ad1

Browse files
committed
SAML 2 Login - Documentation
Fixes gh-7472 #7472
1 parent fc8a018 commit 8bc3ad1

File tree

3 files changed

+317
-0
lines changed

3 files changed

+317
-0
lines changed

docs/manual/src/docs/asciidoc/_includes/servlet/index.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ include::authorization/index.adoc[leveloffset=+1]
1212

1313
include::oauth2/index.adoc[leveloffset=+1]
1414

15+
include::saml2/index.adoc[leveloffset=+1]
16+
1517
include::exploits/index.adoc[leveloffset=+1]
1618

1719
include::integrations/index.adoc[leveloffset=+1]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
= SAML2
2+
3+
include::saml2-login.adoc[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
[[saml2login]]
2+
== SAML 2.0 Login
3+
4+
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).
5+
6+
NOTE: SAML 2.0 Login is implemented by using the *Web Browser SSO Profile*, as specified in
7+
https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf#page=15[SAML 2 Profiles].
8+
Our implementation is currently limited to a simple authentication scheme.
9+
10+
[[saml2login-spring-security-saml2-history]]
11+
=== SAML 2 Support in Spring Security
12+
13+
SAML 2 Service Provider, SP a.k.a. a relying party, support existed as an
14+
https://github.com/spring-projects/spring-security-saml/tree/1e013b07a7772defd6a26fcfae187c9bf661ee8f#spring-saml[independent project]
15+
since 2009. The 1.0.x branch is still in use, including in the
16+
https://github.com/cloudfoundry/uaa[Cloud Foundry User Account and Authentication Server] that
17+
also created a SAML 2.0 Identity Provider implementation based on the SP implementation.
18+
19+
In 2018 we experimented with creating an updated implementation of both a
20+
https://github.com/spring-projects/spring-security-saml#spring-saml[Service Provider and Identity Provider]
21+
as a standalone library. After careful, and lengthy, deliberation we, the Spring Security team, decided
22+
to discontinue that effort. While this effort created a replacement for that standalone 1.0.x library
23+
we didn't feel that we should build a library on top of another library.
24+
25+
Instead we opted to provide framework support for SAML 2 authentication as part of
26+
https://github.com/spring-projects/spring-security[core Spring Security] instead.
27+
28+
[[samllogin-concepts]]
29+
=== Saml 2 Login - High Level Concepts
30+
31+
`saml2Login()` is aimed to support a fraction of the https://saml.xml.org/saml-specifications[SAML 2 feature set]
32+
with a focus on authentication being a Service Provider, SP, a relying party, receiving XML assertions from an
33+
Identity Provider, aka an asserting party.
34+
35+
A SAML 2 login, or authentication, is the concept that the SP receives and validates an XML message called
36+
an assertion from an IDP.
37+
38+
There are currently two supported authentication flows
39+
40+
1. IDP Initiated flow - example: You login in directly to Okta, and then select a web application to be authenticated for.
41+
Okta, the IDP, sends an assertion to the web application, the SP.
42+
2. SP Initiated flow - example: You access a web application, a SP, the application sends an
43+
authentication request to the IDP requesting an assertion. Upon successful authentication on the IDP,
44+
the IDP sends an assertion to the SP.
45+
46+
[[samllogin-feature-set]]
47+
=== Saml 2 Login - Current Feature Set
48+
49+
1. Service Provider (SP/Relying Party) is identified by `entityId = {baseUrl}/saml2/service-provider-metadata/{registrationId}`
50+
2. Receive assertion embedded in a SAML response via Http-POST or Http-Redirect at `{baseUrl}/login/saml2/sso/{registrationId}`
51+
3. Requires the assertion to be signed, unless the response is signed
52+
4. Supports encrypted assertions
53+
5. Supports encrypted NameId elements
54+
6. Allows for extraction of assertion attributes into authorities using a `Converter<Assertion, Collection<? extends GrantedAuthority>>`
55+
7. Allows mapping and white listing of authorities using a `GrantedAuthoritiesMapper`
56+
8. Public keys in `java.security.cert.X509Certificate` format.
57+
9. SP Initiated Authentication via an `AuthNRequest`
58+
59+
==== Saml 2 Login - Not Yet Supported
60+
61+
1. Mappings assertion conditions and attributes to session features (timeout, tracking, etc)
62+
2. Single logout
63+
3. Dynamic metadata generation
64+
4. Receiving and validating standalone assertion (not wrapped in a response object)
65+
66+
[[samllogin-introduction-java-config]]
67+
=== Saml 2 Login - Introduction to Java Configuration
68+
69+
To add `saml2Login()` to a Spring Security filter chain,
70+
the minimal Java configuration requires a configuration repository,
71+
the `RelyingPartyRegistrationRepository`, that contains the SAML configuration and
72+
the invocation of the `HttpSecurity.saml2Login()` method:
73+
[source,java]
74+
----
75+
@EnableWebSecurity
76+
public class SecurityConfig extends WebSecurityConfigurerAdapter {
77+
78+
@Bean
79+
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
80+
//SAML configuration
81+
//Mapping this application to one or more Identity Providers
82+
return new InMemoryRelyingPartyRegistrationRepository(...);
83+
}
84+
85+
@Override
86+
protected void configure(HttpSecurity http) throws Exception {
87+
http
88+
.authorizeRequests()
89+
.anyRequest().authenticated()
90+
.and()
91+
.saml2Login()
92+
;
93+
}
94+
}
95+
----
96+
97+
The bean declaration is a convenient, but optional, approach.
98+
You can directly wire up the repository using a method call
99+
[source,java]
100+
----
101+
@EnableWebSecurity
102+
public class SecurityConfig extends WebSecurityConfigurerAdapter {
103+
104+
@Override
105+
protected void configure(HttpSecurity http) throws Exception {
106+
http
107+
.authorizeRequests()
108+
.anyRequest().authenticated()
109+
.and()
110+
.saml2Login()
111+
.relyingPartyRegistrationRepository(...)
112+
;
113+
}
114+
}
115+
----
116+
117+
==== RelyingPartyRegistration
118+
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`]
119+
object represents the mapping between this application, the SP, and the asserting party, the IDP.
120+
121+
===== URI Patterns
122+
123+
URI patterns are frequenty used to automatically generate URIs based on
124+
an incoming request. The URI patterns in `saml2Login` can contain the following variables
125+
126+
* `baseUrl`
127+
* `registrationId`
128+
* `baseScheme`
129+
* `baseHost`
130+
* `basePort`
131+
132+
For example:
133+
```
134+
{baseUrl}/login/saml2/sso/{registrationId}
135+
```
136+
137+
===== Relying Party
138+
139+
140+
* `registrationId` - (required) a unique identifer for this configuration mapping.
141+
This identifier may be used in URI paths, so care should be taken that no URI encoding is required.
142+
* `localEntityIdTemplate` - (optional) A URI pattern that creates an entity ID for this application based on the incoming request. The default is
143+
`{baseUrl}/saml2/service-provider-metadata/{registrationId}` and for a small sample application
144+
it would look like
145+
```
146+
http://localhost:8080/saml2/service-provider-metadata/my-test-configuration
147+
```
148+
There is no requirement that this configuration option is a pattern, it can be a fixed URI value.
149+
150+
* `remoteIdpEntityId` - (required) the entity ID of the Identity Provider. Always a fixed URI value or string,
151+
no patterns allowed.
152+
* `assertionConsumerServiceUrlTemplate` - (optional) A URI pattern that denotes the assertion
153+
consumer service URI to be sent with any `AuthNRequest` from the SP to the IDP during the SP initiated flow.
154+
While this can be a pattern the actual URI must resolve to the ACS endpoint on the SP.
155+
The default value is `{baseUrl}/login/saml2/sso/{registrationId}` and maps directly to the
156+
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
157+
* `idpWebSsoUrl` - (required) a fixed URI value for the IDP Single Sign On endpoint where
158+
the SP sends the `AuthNRequest` messages.
159+
* `credentials` - A list of credentials, private keys and x509 certificates, used for
160+
message signing, verification, encryption and decryption.
161+
This list can contain redundant credentials to allow for easy rotation of credentials.
162+
For example
163+
** [0] - X509Certificate{VERIFICATION,ENCRYPTION} - The IDP's first public key used for
164+
verification and encryption.
165+
** [1] - X509Certificate/{VERIFICATION,ENCRYPTION} - The IDP's second verification key used for verification.
166+
Encryption is always done using the first `ENCRYPTION` key in the list.
167+
** [2] - PrivateKey/X509Certificate{SIGNING,DECRYPTION} - The SP's first signing and decryption credential.
168+
** [3] - PrivateKey/X509Certificate{SIGNING,DECRYPTION} - The SP's second decryption credential.
169+
Signing is always done using the first `SIGNING` key in the list.
170+
171+
When an incoming message is received, signatures are always required, the system will first attempt
172+
to validate the signature using the certificate at index [0] and only move to the second
173+
credential if the first one fails.
174+
175+
In a similar fashion, the SP configured private keys are used for decryption and attempted in the same order.
176+
The first SP credential (`type=SIGNING`) will be used when messages to the IDP are signed.
177+
178+
===== Duplicated Relying Party Configurations
179+
180+
In the use case where an application uses multiple identity providers it becomes
181+
obvious that some configuration is duplicated between two `RelyingPartyRegistration` objects
182+
183+
* localEntityIdTemplate
184+
* credentials (all SP credentials, IDP credentials change)
185+
* assertionConsumerServiceUrlTemplate
186+
187+
While there is some drawback in duplicating configuration values the back end
188+
configuration repository does not need to replicate this data storage model.
189+
190+
There is a benefit that comes with this setup. Credentials may be more easily rotated
191+
for some identity providers vs others. This object model can ensure that there is no
192+
disruption when configuration is changed in a multi IDP use case and you're not able to rotate
193+
credentials on all the identity providers.
194+
195+
==== Service Provider Metadata
196+
197+
The Spring Security SAML 2 implementation does not yet provide an endpoint for downloading
198+
SP metadata in XML format. The minimal pieces that are exchanged
199+
200+
* *entity ID* - defaults to `{baseUrl}/saml2/service-provider-metadata/{registrationId}`
201+
Other known configuration names that also use this same value
202+
** Audience Restriction
203+
* *single signon URL* - defaults to `{baseUrl}/login/saml2/sso/{registrationId}`
204+
Other known configuration names that also use this same value
205+
** Recipient URL
206+
** Destination URL
207+
** Assertion Consumer Service URL
208+
* X509Certificate - the certificate that you configure as part of your {SIGNING,DECRYPTION}
209+
credentials must be shared with the Identity Provider
210+
211+
==== Authentication Requests - SP Initiated Flow
212+
213+
To initiate an authentication from the web application, a simple redirect to
214+
```
215+
{baseUrl}/saml2/authenticate/{registrationId}
216+
```
217+
The endpoint will generate an `AuthNRequest` by invoking the `createAuthenticationRequest` method on a
218+
configurable factory. Just expose the `Saml2AuthenticationRequestFactory` as a bean in your configuration.
219+
[source,java]
220+
----
221+
public interface Saml2AuthenticationRequestFactory {
222+
String createAuthenticationRequest(Saml2AuthenticationRequest request);
223+
}
224+
----
225+
226+
[[samllogin-sample-boot]]
227+
=== Spring Boot 2.x Sample
228+
229+
We are currently working with the the Spring Boot team on the
230+
https://github.com/spring-projects/spring-boot/issues/18260[Auto Configuration for Spring Security SAML Login].
231+
In the meantime, we have provided a Spring Boot sample that supports a Yaml configuration.
232+
233+
To run the sample, follow these three steps
234+
235+
1. Launch the Spring Boot application
236+
** `./gradlew :spring-security-samples-boot-saml2login:bootRun`
237+
2. Open a browser
238+
** http://localhost:8080/[http://localhost:8080/]
239+
3. This will take you to an identity provider, log in using:
240+
** User: `user`
241+
** Password: `password`
242+
243+
==== Multiple Identity Provider Sample
244+
245+
It's very simple to use multiple providers, but there are some defaults that
246+
may trip you up if you don't pay attention. In our SAML configuration of
247+
`RelyingPartyRegistration` objects, we default an SP entity ID to
248+
```
249+
{baseUrl}/saml2/service-provider-metadata/{registrationId}
250+
```
251+
252+
That means in our two provider configuration, our system would look like
253+
254+
```
255+
registration-1 (Identity Provider 1) - Our local SP Entity ID is:
256+
http://localhost:8080/saml2/service-provider-metadata/registration-1
257+
258+
registration-2 (Identity Provider 2) - Our local SP Entity ID is:
259+
http://localhost:8080/saml2/service-provider-metadata/registration-2
260+
```
261+
262+
In this configuration, illustrated in the sample below, to the outside world,
263+
we have actually created two virtual Service Provider identities
264+
hosted within the same application.
265+
266+
[source,yaml]
267+
----
268+
spring:
269+
security:
270+
saml2:
271+
login:
272+
relying-parties:
273+
- entity-id: &idp-entity-id https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php
274+
registration-id: simplesamlphp
275+
web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php
276+
signing-credentials: &service-provider-credentials
277+
- private-key: |
278+
-----BEGIN PRIVATE KEY-----
279+
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE
280+
...................SHORTENED FOR READ ABILITY...................
281+
INrtuLp4YHbgk1mi
282+
-----END PRIVATE KEY-----
283+
certificate: |
284+
-----BEGIN CERTIFICATE-----
285+
MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC
286+
...................SHORTENED FOR READ ABILITY...................
287+
RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B
288+
-----END CERTIFICATE-----
289+
verification-credentials: &idp-certificates
290+
- |
291+
-----BEGIN CERTIFICATE-----
292+
MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD
293+
...................SHORTENED FOR READ ABILITY...................
294+
lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk
295+
-----END CERTIFICATE-----
296+
- entity-id: *idp-entity-id
297+
registration-id: simplesamlphp2
298+
web-sso-url: *idp-sso-url
299+
signing-credentials: *service-provider-credentials
300+
verification-credentials: *idp-certificates
301+
----
302+
303+
If this is not desirable, you can manually override the local SP entity ID by using the
304+
```
305+
localEntityIdTemplate = {baseUrl}/saml2/service-provider-metadata
306+
```
307+
If we change our local SP entity ID to this value, it is still important that we give
308+
out the correct single sign on URL (the assertion consumer service URL)
309+
for each registered identity provider based on the registration Id.
310+
`{baseUrl}/login/saml2/sso/{registrationId}`
311+
312+

0 commit comments

Comments
 (0)