|
| 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