Skip to content

Commit 60c42e3

Browse files
committed
Update SAML 2.0 Documentation to use OpenSAML 5
Closes gh-17707
1 parent 5506c48 commit 60c42e3

File tree

6 files changed

+28
-250
lines changed

6 files changed

+28
-250
lines changed

docs/modules/ROOT/pages/migration/servlet/oauth2.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fun jwtDecoder(): JwtDecoder {
8383

8484
Spring Security does not support processing `<saml2:Response>` payloads over GET as this is not supported by the SAML 2.0 spec.
8585

86-
To better comply with this, `Saml2AuthenticationTokenConverter`, `OpenSaml4AuthenticationTokenConverter`, and `OpenSaml5AuthenticationTokenConverter` will not process GET requests by default as of Spring Security 8.
86+
To better comply with this, `Saml2AuthenticationTokenConverter` and `OpenSaml5AuthenticationTokenConverter` will not process GET requests by default as of Spring Security 8.
8787
To prepare for this, the property `shouldConvertGetRequests` is available.
8888
To use it, publish your own converter like so:
8989

@@ -114,7 +114,7 @@ fun authenticationConverter(val registrations: RelyingPartyRegistrationRepositor
114114
----
115115
======
116116

117-
If you must continue using `Saml2AuthenticationTokenConverter`, `OpenSaml4AuthenticationTokenConverter`, or `OpenSaml5AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.`
117+
If you must continue using `Saml2AuthenticationTokenConverter` or `OpenSaml5AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.`
118118

119119
== Provide an AuthenticationConverter to BearerTokenAuthenticationFilter
120120

docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ var relyingPartyRegistration: RelyingPartyRegistration? =
267267
There are a number of reasons that you may want to adjust an `AuthnRequest`.
268268
For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default.
269269

270-
You can customize elements of OpenSAML's `AuthnRequest` by publishing an `OpenSaml4AuthenticationRequestResolver` as a `@Bean`, like so:
270+
You can customize elements of OpenSAML's `AuthnRequest` by publishing an `OpenSaml5AuthenticationRequestResolver` as a `@Bean`, like so:
271271

272272
[tabs]
273273
======
@@ -279,8 +279,8 @@ Java::
279279
Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
280280
RelyingPartyRegistrationResolver registrationResolver =
281281
new DefaultRelyingPartyRegistrationResolver(registrations);
282-
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver =
283-
new OpenSaml4AuthenticationRequestResolver(registrationResolver);
282+
OpenSaml5AuthenticationRequestResolver authenticationRequestResolver =
283+
new OpenSaml5AuthenticationRequestResolver(registrationResolver);
284284
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
285285
.getAuthnRequest().setForceAuthn(true));
286286
return authenticationRequestResolver;
@@ -295,8 +295,8 @@ Kotlin::
295295
fun authenticationRequestResolver(registrations : RelyingPartyRegistrationRepository) : Saml2AuthenticationRequestResolver {
296296
val registrationResolver : RelyingPartyRegistrationResolver =
297297
new DefaultRelyingPartyRegistrationResolver(registrations)
298-
val authenticationRequestResolver : OpenSaml4AuthenticationRequestResolver =
299-
new OpenSaml4AuthenticationRequestResolver(registrationResolver)
298+
val authenticationRequestResolver : OpenSaml5AuthenticationRequestResolver =
299+
new OpenSaml5AuthenticationRequestResolver(registrationResolver)
300300
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
301301
.getAuthnRequest().setForceAuthn(true))
302302
return authenticationRequestResolver

docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc

Lines changed: 10 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[[servlet-saml2login-authenticate-responses]]
22
= Authenticating ``<saml2:Response>``s
33

4-
To verify SAML 2.0 Responses, Spring Security uses xref:servlet/saml2/login/overview.adoc#servlet-saml2login-authentication-saml2authenticationtokenconverter[`Saml2AuthenticationTokenConverter`] to populate the `Authentication` request and xref:servlet/saml2/login/overview.adoc#servlet-saml2login-architecture[`OpenSaml4AuthenticationProvider`] to authenticate it.
4+
To verify SAML 2.0 Responses, Spring Security uses xref:servlet/saml2/login/overview.adoc#servlet-saml2login-authentication-saml2authenticationtokenconverter[`Saml2AuthenticationTokenConverter`] to populate the `Authentication` request and xref:servlet/saml2/login/overview.adoc#servlet-saml2login-architecture[`OpenSaml5AuthenticationProvider`] to authenticate it.
55

66
You can configure this in a number of ways including:
77

@@ -123,76 +123,7 @@ fun securityFilters(val http: HttpSecurity, val converter: AuthenticationConvert
123123
== Setting a Clock Skew
124124

125125
It's not uncommon for the asserting and relying parties to have system clocks that aren't perfectly synchronized.
126-
For that reason, you can configure ``OpenSaml4AuthenticationProvider``'s default assertion validator with some tolerance:
127-
128-
[tabs]
129-
======
130-
Java::
131-
+
132-
[source,java,role="primary"]
133-
----
134-
@Configuration
135-
@EnableWebSecurity
136-
public class SecurityConfig {
137-
138-
@Bean
139-
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
140-
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
141-
authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider
142-
.createDefaultAssertionValidatorWithParameters(assertionToken -> {
143-
Map<String, Object> params = new HashMap<>();
144-
params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());
145-
// ... other validation parameters
146-
return new ValidationContext(params);
147-
})
148-
);
149-
150-
http
151-
.authorizeHttpRequests((authorize) -> authorize
152-
.anyRequest().authenticated()
153-
)
154-
.saml2Login((saml2) -> saml2
155-
.authenticationManager(new ProviderManager(authenticationProvider))
156-
);
157-
return http.build();
158-
}
159-
}
160-
----
161-
162-
Kotlin::
163-
+
164-
[source,kotlin,role="secondary"]
165-
----
166-
@Configuration
167-
@EnableWebSecurity
168-
open class SecurityConfig {
169-
@Bean
170-
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
171-
val authenticationProvider = OpenSaml4AuthenticationProvider()
172-
authenticationProvider.setAssertionValidator(
173-
OpenSaml4AuthenticationProvider
174-
.createDefaultAssertionValidatorWithParameters(Converter<OpenSaml4AuthenticationProvider.AssertionToken, ValidationContext> {
175-
val params: MutableMap<String, Any> = HashMap()
176-
params[CLOCK_SKEW] =
177-
Duration.ofMinutes(10).toMillis()
178-
ValidationContext(params)
179-
})
180-
)
181-
http {
182-
authorizeHttpRequests {
183-
authorize(anyRequest, authenticated)
184-
}
185-
saml2Login {
186-
authenticationManager = ProviderManager(authenticationProvider)
187-
}
188-
}
189-
return http.build()
190-
}
191-
}
192-
----
193-
======
194-
195-
If you are using xref:servlet/saml2/opensaml.adoc[OpenSAML 5], then we have a simpler way, using `OpenSaml5AuthenticationProvider.AssertionValidator`:
126+
For that reason, you can configure `OpenSaml5AuthenticationProvider.AssertionValidator` as follows:
196127

197128
[tabs]
198129
======
@@ -381,86 +312,8 @@ open class MyUserDetailsResponseAuthenticationConverter(val delegate: ResponseAu
381312
If your `UserDetailsService` returns a value that also implements `AuthenticatedPrincipal`, then you don't need a custom authentication implementation.
382313
====
383314

384-
Or, if you are using OpenSaml 4, then you can achieve something similar as follows:
385-
386-
[tabs]
387-
======
388-
Java::
389-
+
390-
[source,java,role="primary"]
391-
----
392-
@Configuration
393-
@EnableWebSecurity
394-
public class SecurityConfig {
395-
@Autowired
396-
UserDetailsService userDetailsService;
397-
398-
@Bean
399-
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
400-
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
401-
authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
402-
Saml2Authentication authentication = OpenSaml4AuthenticationProvider
403-
.createDefaultResponseAuthenticationConverter() <1>
404-
.convert(responseToken);
405-
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
406-
String username = assertion.getSubject().getNameID().getValue();
407-
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); <2>
408-
return MySaml2Authentication(userDetails, authentication); <3>
409-
});
410-
411-
http
412-
.authorizeHttpRequests((authorize) -> authorize
413-
.anyRequest().authenticated()
414-
)
415-
.saml2Login((saml2) -> saml2
416-
.authenticationManager(new ProviderManager(authenticationProvider))
417-
);
418-
return http.build();
419-
}
420-
}
421-
----
422-
423-
Kotlin::
424-
+
425-
[source,kotlin,role="secondary"]
426-
----
427-
@Configuration
428-
@EnableWebSecurity
429-
open class SecurityConfig {
430-
@Autowired
431-
var userDetailsService: UserDetailsService? = null
432-
433-
@Bean
434-
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
435-
val authenticationProvider = OpenSaml4AuthenticationProvider()
436-
authenticationProvider.setResponseAuthenticationConverter { responseToken: OpenSaml4AuthenticationProvider.ResponseToken ->
437-
val authentication = OpenSaml4AuthenticationProvider
438-
.createDefaultResponseAuthenticationConverter() <1>
439-
.convert(responseToken)
440-
val assertion: Assertion = responseToken.response.assertions[0]
441-
val username: String = assertion.subject.nameID.value
442-
val userDetails = userDetailsService!!.loadUserByUsername(username) <2>
443-
MySaml2Authentication(userDetails, authentication) <3>
444-
}
445-
http {
446-
authorizeHttpRequests {
447-
authorize(anyRequest, authenticated)
448-
}
449-
saml2Login {
450-
authenticationManager = ProviderManager(authenticationProvider)
451-
}
452-
}
453-
return http.build()
454-
}
455-
}
456-
----
457-
======
458-
<1> First, call the default converter, which extracts attributes and authorities from the response
459-
<2> Second, call the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] using the relevant information
460-
<3> Third, return a custom authentication that includes the user details
461-
462315
[NOTE]
463-
It's not required to call ``OpenSaml4AuthenticationProvider``'s default authentication converter.
316+
It's not required to call ``OpenSaml5AuthenticationProvider``'s default authentication converter.
464317
It returns a `Saml2AuthenticatedPrincipal` containing the attributes it extracted from ``AttributeStatement``s as well as the single `ROLE_USER` authority.
465318

466319
=== Configuring the Principal Name
@@ -538,28 +391,10 @@ fun authenticationConverter(): ResponseAuthenticationConverter {
538391
[[servlet-saml2login-opensamlauthenticationprovider-additionalvalidation]]
539392
== Performing Additional Response Validation
540393

541-
`OpenSaml4AuthenticationProvider` validates the `Issuer` and `Destination` values right after decrypting the `Response`.
394+
`OpenSaml5AuthenticationProvider` validates the `Issuer` and `Destination` values right after decrypting the `Response`.
542395
You can customize the validation by extending the default validator concatenating with your own response validator, or you can replace it entirely with yours.
543396

544397
For example, you can throw a custom exception with any additional information available in the `Response` object, like so:
545-
[source,java]
546-
----
547-
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
548-
provider.setResponseValidator((responseToken) -> {
549-
Saml2ResponseValidatorResult result = OpenSaml4AuthenticationProvider
550-
.createDefaultResponseValidator()
551-
.convert(responseToken)
552-
.concat(myCustomValidator.convert(responseToken));
553-
if (!result.getErrors().isEmpty()) {
554-
String inResponseTo = responseToken.getInResponseTo();
555-
throw new CustomSaml2AuthenticationException(result, inResponseTo);
556-
}
557-
return result;
558-
});
559-
----
560-
561-
When using `OpenSaml5AuthenticationProvider`, you can do the same with less boilerplate:
562-
563398
[source,java]
564399
----
565400
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
@@ -583,74 +418,17 @@ OpenSAML performs `Asssertion#InResponseTo` validation in its `BearerSubjectConf
583418
====
584419

585420
== Performing Additional Assertion Validation
586-
`OpenSaml4AuthenticationProvider` performs minimal validation on SAML 2.0 Assertions.
421+
`OpenSaml5AuthenticationProvider` performs minimal validation on SAML 2.0 Assertions.
587422
After verifying the signature, it will:
588423

589424
1. Validate `<AudienceRestriction>` and `<DelegationRestriction>` conditions
590425
2. Validate ``<SubjectConfirmation>``s, expect for any IP address information
591426

592-
To perform additional validation, you can configure your own assertion validator that delegates to ``OpenSaml4AuthenticationProvider``'s default and then performs its own.
427+
To perform additional validation, you can configure your own assertion validator that delegates to ``OpenSaml5AuthenticationProvider``'s default and then performs its own.
593428

594429
[[servlet-saml2login-opensamlauthenticationprovider-onetimeuse]]
595430
For example, you can use OpenSAML's `OneTimeUseConditionValidator` to also validate a `<OneTimeUse>` condition, like so:
596431

597-
[tabs]
598-
======
599-
Java::
600-
+
601-
[source,java,role="primary"]
602-
----
603-
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
604-
OneTimeUseConditionValidator validator = ...;
605-
provider.setAssertionValidator(assertionToken -> {
606-
Saml2ResponseValidatorResult result = OpenSaml4AuthenticationProvider
607-
.createDefaultAssertionValidator()
608-
.convert(assertionToken);
609-
Assertion assertion = assertionToken.getAssertion();
610-
OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse();
611-
ValidationContext context = new ValidationContext();
612-
try {
613-
if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {
614-
return result;
615-
}
616-
} catch (Exception e) {
617-
return result.concat(new Saml2Error(INVALID_ASSERTION, e.getMessage()));
618-
}
619-
return result.concat(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage()));
620-
});
621-
----
622-
623-
Kotlin::
624-
+
625-
[source,kotlin,role="secondary"]
626-
----
627-
var provider = OpenSaml4AuthenticationProvider()
628-
var validator: OneTimeUseConditionValidator = ...
629-
provider.setAssertionValidator { assertionToken ->
630-
val result = OpenSaml4AuthenticationProvider
631-
.createDefaultAssertionValidator()
632-
.convert(assertionToken)
633-
val assertion: Assertion = assertionToken.assertion
634-
val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse
635-
val context = ValidationContext()
636-
try {
637-
if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {
638-
return@setAssertionValidator result
639-
}
640-
} catch (e: Exception) {
641-
return@setAssertionValidator result.concat(Saml2Error(INVALID_ASSERTION, e.message))
642-
}
643-
result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))
644-
}
645-
----
646-
======
647-
648-
[NOTE]
649-
While recommended, it's not necessary to call ``OpenSaml4AuthenticationProvider``'s default assertion validator.
650-
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.
651-
652-
If you are using xref:servlet/saml2/opensaml.adoc[OpenSAML 5], then we have a simpler way using `OpenSaml5AuthenticationProvider.AssertionValidator`:
653-
654432
[tabs]
655433
======
656434
Java::
@@ -708,11 +486,11 @@ provider.setAssertionValidator(assertionValidator)
708486

709487
Spring Security decrypts `<saml2:EncryptedAssertion>`, `<saml2:EncryptedAttribute>`, and `<saml2:EncryptedID>` elements automatically by using the decryption xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-credentials[`Saml2X509Credential` instances] registered in the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`].
710488

711-
`OpenSaml4AuthenticationProvider` exposes xref:servlet/saml2/login/overview.adoc#servlet-saml2login-architecture[two decryption strategies].
489+
`OpenSaml5AuthenticationProvider` exposes xref:servlet/saml2/login/overview.adoc#servlet-saml2login-architecture[two decryption strategies].
712490
The response decrypter is for decrypting encrypted elements of the `<saml2:Response>`, like `<saml2:EncryptedAssertion>`.
713491
The assertion decrypter is for decrypting encrypted elements of the `<saml2:Assertion>`, like `<saml2:EncryptedAttribute>` and `<saml2:EncryptedID>`.
714492

715-
You can replace ``OpenSaml4AuthenticationProvider``'s default decryption strategy with your own.
493+
You can replace ``OpenSaml5AuthenticationProvider``'s default decryption strategy with your own.
716494
For example, if you have a separate service that decrypts the assertions in a `<saml2:Response>`, you can use it instead like so:
717495

718496
[tabs]
@@ -722,7 +500,7 @@ Java::
722500
[source,java,role="primary"]
723501
----
724502
MyDecryptionService decryptionService = ...;
725-
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
503+
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
726504
provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));
727505
----
728506
@@ -731,7 +509,7 @@ Kotlin::
731509
[source,kotlin,role="secondary"]
732510
----
733511
val decryptionService: MyDecryptionService = ...
734-
val provider = OpenSaml4AuthenticationProvider()
512+
val provider = OpenSaml5AuthenticationProvider()
735513
provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }
736514
----
737515
======

docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ This filter calls its configured `AuthenticationConverter` to create a `Saml2Aut
4949
This converter additionally resolves the <<servlet-saml2login-relyingpartyregistration, `RelyingPartyRegistration`>> and supplies it to `Saml2AuthenticationToken`.
5050

5151
image:{icondir}/number_2.png[] Next, the filter passes the token to its configured xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`AuthenticationManager`].
52-
By default, it uses the <<servlet-saml2login-architecture,`OpenSaml4AuthenticationProvider`>>.
52+
By default, it uses the <<servlet-saml2login-architecture,`OpenSaml5AuthenticationProvider`>>.
5353

5454
image:{icondir}/number_3.png[] If authentication fails, then _Failure_.
5555

0 commit comments

Comments
 (0)