| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 | 
							- = Saml 2.0 Migrations
 
- == Use OpenSAML 5 By Default
 
- OpenSAML 4.x is no longer supported by the OpenSAML team.
 
- As such, Spring Security will default to using its `OpenSaml5` components in all cases.
 
- If you want to see how well your application will respond to this, do the following:
 
- 1. Update your OpenSAML dependencies to 5.x
 
- 2. If you are constructing an `OpenSaml4XXX` Spring Security component, change it to `OpenSaml5`.
 
- If you cannot opt-in, then add the `opensaml-saml-api` and `opensaml-saml-impl` 4.x dependencies and exclude the 5.x dependencies from `spring-security-saml2-service-provider`.
 
- == Continue Filter Chain When No Relying Party Found
 
- In Spring Security 6, `Saml2WebSsoAuthenticationFilter` throws an exception when the request URI matches, but no relying party registration is found.
 
- There are a number of cases when an application would not consider this an error situation.
 
- For example, this filter doesn't know how the `AuthorizationFilter` will respond to a missing relying party.
 
- In some cases it may be allowable.
 
- In other cases, you may want your `AuthenticationEntryPoint` to be invoked, which would happen if this filter were to allow the request to continue to the `AuthorizationFilter`.
 
- To improve this filter's flexibility, in Spring Security 7 it will continue the filter chain when there is no relying party registration found instead of throwing an exception.
 
- For many applications, the only notable change will be that your `authenticationEntryPoint` will be invoked if the relying party registration cannot be found.
 
- When you have only one asserting party, this means by default a new authentication request will be built and sent back to the asserting party, which may cause a "Too Many Redirects" loop.
 
- To see if you are affected in this way, you can prepare for this change in 6 by setting the following property in `Saml2WebSsoAuthenticationFilter`:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- http
 
-     .saml2Login((saml2) -> saml2
 
-         .withObjectPostProcessor(new ObjectPostProcessor<Saml2WebSsoAuhenticaionFilter>() {
 
- 			@Override
 
-             public Saml2WebSsoAuthenticationFilter postProcess(Saml2WebSsoAuthenticationFilter filter) {
 
- 				filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true);
 
- 				return filter;
 
-             }
 
-         })
 
-     )
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- http {
 
-     saml2Login { }
 
-     withObjectPostProcessor(
 
-         object : ObjectPostProcessor<Saml2WebSsoAuhenticaionFilter?>() {
 
-             override fun postProcess(filter: Saml2WebSsoAuthenticationFilter): Saml2WebSsoAuthenticationFilter {
 
-             filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true)
 
-             return filter
 
-         }
 
-     })
 
- }
 
- ----
 
- Xml::
 
- +
 
- [source,xml,role="secondary"]
 
- ----
 
- <b:bean id="saml2PostProcessor" class="org.example.MySaml2WebSsoAuthenticationFilterBeanPostProcessor"/>
 
- ----
 
- ======
 
- == Validate Response After Validating Assertions
 
- In Spring Security 6, the order of authenticating a `<saml2:Response>` is as follows:
 
- 1. Verify the Response Signature, if any
 
- 2. Decrypt the Response
 
- 3. Validate Response attributes, like Destination and Issuer
 
- 4. For each assertion, verify the signature, decrypt, and then validate its fields
 
- 5. Check to ensure that the response has at least one assertion with a name field
 
- This ordering sometimes poses challenges since some response validation is being done in Step 3 and some in Step 5.
 
- Specifically, this poses a chellenge when an application doesn't have a name field and doesn't need it to be validated.
 
- In Spring Security 7, this is simplified by moving response validation to after assertion validation and combining the two separate validation steps 3 and 5.
 
- When this is complete, response validation will no longer check for the existence of the `NameID` attribute and rely on ``ResponseAuthenticationConverter``s to do this.
 
- This will add support ``ResponseAuthenticationConverter``s that don't use the `NameID` element in their `Authentication` instance and so don't need it validated.
 
- To opt-in to this behavior in advance, use `OpenSaml5AuthenticationProvider#setValidateResponseAfterAssertions` to `true` like so:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
 
- provider.setValidateResponseAfterAssertions(true);
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val provider = OpenSaml5AuthenticationProvider()
 
- provider.setValidateResponseAfterAssertions(true)
 
- ----
 
- ======
 
- This will change the authentication steps as follows:
 
- 1. Verify the Response Signature, if any
 
- 2. Decrypt the Response
 
- 3. For each assertion, verify the signature, decrypt, and then validate its fields
 
- 4. Validate Response attributes, like Destination and Issuer
 
- Note that if you have a custom response authentication converter, then you are now responsible to check if the `NameID` element exists in the event that you need it.
 
- Alternatively to updating your response authentication converter, you can specify a custom `ResponseValidator` that adds back in the check for the `NameID` element as follows:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
 
- provider.setValidateResponseAfterAssertions(true);
 
- ResponseValidator responseValidator = ResponseValidator.withDefaults((responseToken) -> {
 
- 	Response response = responseToken.getResponse();
 
- 	Assertion assertion = CollectionUtils.firstElement(response.getAssertions());
 
- 	Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
 
-             "Assertion [" + firstAssertion.getID() + "] is missing a subject");
 
- 	Saml2ResponseValidationResult failed = Saml2ResponseValidationResult.failure(error);
 
- 	if (assertion.getSubject() == null) {
 
- 		return failed;
 
- 	}
 
- 	if (assertion.getSubject().getNameID() == null) {
 
- 		return failed;
 
- 	}
 
- 	if (assertion.getSubject().getNameID().getValue() == null) {
 
- 		return failed;
 
- 	}
 
- 	return Saml2ResponseValidationResult.success();
 
- });
 
- provider.setResponseValidator(responseValidator);
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val provider = OpenSaml5AuthenticationProvider()
 
- provider.setValidateResponseAfterAssertions(true)
 
- val responseValidator = ResponseValidator.withDefaults { responseToken: ResponseToken ->
 
- 	val response = responseToken.getResponse()
 
- 	val assertion = CollectionUtils.firstElement(response.getAssertions())
 
- 	val error = Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
 
-         "Assertion [" + firstAssertion.getID() + "] is missing a subject")
 
- 	val failed = Saml2ResponseValidationResult.failure(error)
 
- 	if (assertion.getSubject() == null) {
 
-         return@withDefaults failed
 
- 	}
 
- 	if (assertion.getSubject().getNameID() == null) {
 
- 		return@withDefaults failed
 
- 	}
 
- 	if (assertion.getSubject().getNameID().getValue() == null) {
 
- 		return@withDefaults failed
 
- 	}
 
- 	return@withDefaults Saml2ResponseValidationResult.success()
 
- }
 
- provider.setResponseValidator(responseValidator)
 
- ----
 
- ======
 
- == `RelyingPartyRegistration` Improvements
 
- `RelyingPartyRegistration` links metadata from a relying party to metadata from an asserting party.
 
- To prepare for some improvements to the API, please take the following steps:
 
- 1. If you are mutating a registration by using `RelyingPartyRegistration#withRelyingPartyRegistration`, instead call `RelyingPartyRegistration#mutate`
 
- 2. If you are providing or retrieving `AssertingPartyDetails`, use `getAssertingPartyMetadata` or `withAssertingPartyMetadata` instead.
 
- == `OpenSaml5AuthenticationProvider` Improvements
 
- Spring Security 7 will remove a handful of static factories from `OpenSaml5AuthenticationProvider` in favor of inner classes.
 
- These inner classes simplify customization of the response validator, the assertion validator, and the response authentication converter.
 
- === Response Validation
 
- Instead of doing:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
 
- 	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
 
- 	saml2.setResponseValidator((responseToken) -> OpenSamlAuthenticationProvider.createDefaultResponseValidator()
 
-             .andThen((result) -> result
 
-                 .concat(myCustomValidator.convert(responseToken))
 
-             ));
 
- 	return saml2;
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
 
- 	val saml2 = OpenSaml5AuthenticationProvider()
 
- 	saml2.setResponseValidator { responseToken -> OpenSamlAuthenticationProvider.createDefaultResponseValidator()
 
-         .andThen { result -> result
 
-             .concat(myCustomValidator.convert(responseToken))
 
-         }
 
-     }
 
- 	return saml2
 
- }
 
- ----
 
- ======
 
- use `OpenSaml5AuthenticationProvider.ResponseValidator`:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
 
- 	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
 
- 	saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator));
 
- 	return saml2;
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
 
- 	val saml2 = OpenSaml5AuthenticationProvider()
 
- 	saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator))
 
- 	return saml2
 
- }
 
- ----
 
- ======
 
- === Assertion Validation
 
- Instead of doing:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
 
- 	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
 
-     authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider
 
-         .createDefaultAssertionValidatorWithParameters(assertionToken -> {
 
-             Map<String, Object> params = new HashMap<>();
 
-             params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());
 
-             // ... other validation parameters
 
-             return new ValidationContext(params);
 
-         })
 
-     );
 
- 	return saml2;
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
 
- 	val saml2 = OpenSaml5AuthenticationProvider()
 
-     authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider
 
-         .createDefaultAssertionValidatorWithParameters { ->
 
-             val params = HashMap<String, Object>()
 
-             params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis())
 
-             // ... other validation parameters
 
-             return ValidationContext(params)
 
-         }
 
-     )
 
- 	return saml2
 
- }
 
- ----
 
- ======
 
- use `OpenSaml5AuthenticationProvider.AssertionValidator`:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
 
- 	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
 
- 	Duration tenMinutes = Duration.ofMinutes(10);
 
-     authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build());
 
- 	return saml2;
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
 
- 	val saml2 = OpenSaml5AuthenticationProvider()
 
- 	val tenMinutes = Duration.ofMinutes(10)
 
-     authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build())
 
- 	return saml2
 
- }
 
- ----
 
- ======
 
- == Response Authentication Converter
 
- Instead of doing:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- Converter<ResponseToken, Saml2Authentication> authenticationConverter() {
 
- 	return (responseToken) -> {
 
- 		Saml2Authentication authentication = OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter()
 
-             .convert(responseToken);
 
- 		// ... work with OpenSAML's Assertion object to extract the principal
 
- 		return new Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities());
 
- 	};
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun authenticationConverter(): Converter<ResponseToken, Saml2Authentication> {
 
-     return { responseToken ->
 
-         val authentication =
 
-             OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken)
 
- 		// ... work with OpenSAML's Assertion object to extract the principal
 
- 		return Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities())
 
-     }
 
- }
 
- ----
 
- ======
 
- use `OpenSaml5AuthenticationProvider.ResponseAuthenticationConverter`:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- ResponseAuthenticationConverter authenticationConverter() {
 
- 	ResponseAuthenticationConverter authenticationConverter = new ResponseAuthenticationConverter();
 
- 	authenticationConverter.setPrincipalNameConverter((assertion) -> {
 
- 		// ... work with OpenSAML's Assertion object to extract the principal
 
- 	});
 
- 	return authenticationConverter;
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun authenticationConverter(): ResponseAuthenticationConverter {
 
-     val authenticationConverter = ResponseAuthenticationConverter()
 
-     authenticationConverter.setPrincipalNameConverter { assertion ->
 
- 		// ... work with OpenSAML's Assertion object to extract the principal
 
-     }
 
-     return authenticationConverter
 
- }
 
- ----
 
- ======
 
 
  |