| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 | = SAML MigrationsThe following steps relate to changes around how to configure SAML 2.0.== Use OpenSAML 4OpenSAML 3 has reached its end-of-life.As such, Spring Security 6 drops support for it, bumping up its OpenSAML baseline to 4.To prepare for the upgrade, update your pom to depend on OpenSAML 4 instead of 3:====.Maven[source,maven,role="primary"]----<dependencyManagement>    <dependency>        <groupId>org.opensaml</groupId>        <artifactId>opensaml-core</artifactId>        <version>4.2.1</version>    </dependency>    <dependency>        <groupId>org.opensaml</groupId>        <artifactId>opensaml-saml-api</artifactId>        <version>4.2.1</version>    </dependency>    <dependency>        <groupId>org.opensaml</groupId>        <artifactId>opensaml-saml-impl</artifactId>        <version>4.2.1</version>    </dependency></dependencyManagement>----.Gradle[source,gradle,role="secondary"]----dependencies {    constraints {        api "org.opensaml:opensaml-core:4.2.1"        api "org.opensaml:opensaml-saml-api:4.2.1"        api "org.opensaml:opensaml-saml-impl:4.2.1"    }}----====You must use at least OpenSAML 4.1.1 to update to Spring Security 6's SAML support.== Use `OpenSaml4AuthenticationProvider`In order to support both OpenSAML 3 and 4 at the same time, Spring Security released `OpenSamlAuthenticationProvider` and `OpenSaml4AuthenticationProvider`.In 6.0, because OpenSAML3 support is removed, `OpenSamlAuthenticationProvider` is removed as well.Not all methods in `OpenSamlAuthenticationProvider` were ported 1-to-1 to `OpenSaml4AuthenticationProvider`.As such, some adjustment will be required to make the challenge.Consider the following representative usage of `OpenSamlAuthenticationProvider`:====.Java[source,java,role="primary"]----OpenSamlAuthenticationProvider versionThree = new OpenSamlAuthenticationProvider();versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor);versionThree.setResponseTimeValidationSkew(myDuration);----.Kotlin[source,kotlin,role="secondary"]----val versionThree: OpenSamlAuthenticationProvider = OpenSamlAuthenticationProvider()versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor)versionThree.setResponseTimeValidationSkew(myDuration)----====This should change to:====.Java[source,java,role="primary"]----Converter<ResponseToken, Saml2Authentication> delegate = OpenSaml4AuthenticationProvider        .createDefaultResponseAuthenticationConverter();OpenSaml4AuthenticationProvider versionFour = new OpenSaml4AuthenticationProvider();versionFour.setResponseAuthenticationConverter((responseToken) -> {	Saml2Authentication authentication = delegate.convert(responseToken);	Assertion assertion = responseToken.getResponse().getAssertions().get(0);	AuthenticatedPrincipal principal = (AuthenticatedPrincipal) authentication.getPrincipal();	Collection<GrantedAuthority> authorities = myAuthoritiesExtractor.convert(assertion);	return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities);});Converter<AssertionToken, Saml2ResponseValidationResult> validator = OpenSaml4AuthenticationProvider        .createDefaultAssertionValidatorWithParameters((p) -> p.put(CLOCK_SKEW, myDuration));versionFour.setAssertionValidator(validator);----.Kotlin[source,kotlin,role="secondary"]----val delegate = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter()val versionFour = OpenSaml4AuthenticationProvider()versionFour.setResponseAuthenticationConverter({    responseToken -> {        val authentication = delegate.convert(responseToken)        val assertion = responseToken.getResponse().getAssertions().get(0)        val principal = (AuthenticatedPrincipal) authentication.getPrincipal()        val authorities = myAuthoritiesExtractor.convert(assertion)        return Saml2Authentication(principal, authentication.getSaml2Response(), authorities)    }})val validator = OpenSaml4AuthenticationProvider        .createDefaultAssertionValidatorWithParameters({ p -> p.put(CLOCK_SKEW, myDuration) })versionFour.setAssertionValidator(validator)----====== Stop Using SAML 2.0 `Converter` constructorsIn an early release of Spring Security's SAML 2.0 support, `Saml2MetadataFilter` and `Saml2AuthenticationTokenConverter` shipped with constructors of type `Converter`.This level of abstraction made it tricky to evolve the class and so a dedicated interface `RelyingPartyRegistrationResolver` was introduced in a later release.In 6.0, the `Converter` constructors are removed.To prepare for this in 5.8, change classes that implement `Converter<HttpServletRequest, RelyingPartyRegistration>` to instead implement `RelyingPartyRegistrationResolver`.== Change to Using `Saml2AuthenticationRequestResolver``Saml2AuthenticationContextResolver` and `Saml2AuthenticationRequestFactory` are removed in 6.0 as is the `Saml2WebSsoAuthenticationRequestFilter` that requires them.They are replaced by `Saml2AuthenticationRequestResolver` and a new constructor in `Saml2WebSsoAuthenticationRequestFilter`.The new interface removes an unnecessary transport object between the two classes.Most applications need do nothing; however, if you use or configure `Saml2AuthenticationRequestContextResolver` or `Saml2AuthenticationRequestFactory`, try the following steps to convert instead use `Saml2AuthenticationRequestResolver`.=== Use `setAuthnRequestCustomizer` instead of `setAuthenticationRequestContextConverter`If you are calling `OpenSaml4AuthenticationReqeustFactory#setAuthenticationRequestContextConverter`, for example, like so:====.Java[source,java,role="primary"]----@BeanSaml2AuthenticationRequestFactory authenticationRequestFactory() {    OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();	factory.setAuthenticationRequestContextConverter((context) -> {        AuthnRequestBuilder authnRequestBuilder =  ConfigurationService.get(XMLObjectProviderRegistry.class)            .getBuilderFactory().getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);		IssuerBuilder issuerBuilder =  ConfigurationService.get(XMLObjectProviderRegistry.class)            .getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);        tring issuer = context.getIssuer();		String destination = context.getDestination();		String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();		String protocolBinding = context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();		AuthnRequest auth = authnRequestBuilder.buildObject();		auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));		auth.setIssueInstant(Instant.now());		auth.setForceAuthn(Boolean.TRUE);		auth.setIsPassive(Boolean.FALSE);		auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);		Issuer iss = issuerBuilder.buildObject();		iss.setValue(issuer);		auth.setIssuer(iss);		auth.setDestination(destination);		auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);	});	return factory;}----====to ensure that ForceAuthn is set to `true`, you can instead do:====.Java[source,java,role="primary"]----@BeanSaml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationResolver registrations) {    OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);	resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest().setForceAuthn(Boolean.TRUE));	return resolver;}----====Also, since `setAuthnRequestCustomizer` has direct access to the `HttpServletRequest`, there is no need for a `Saml2AuthenticationRequestContextResolver`.Simply use `setAuthnRequestCustomizer` to read directly from `HttpServletRequest` this information you need.=== Use `setAuthnRequestCustomizer` instead of `setProtocolBinding`Instead of doing:====.Java[source,java,role="primary"]----@BeanSaml2AuthenticationRequestFactory authenticationRequestFactory() {    OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();	factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")	return factory;}----====you can do:====.Java[source,java,role="primary"]----@BeanSaml2AuthenticationRequestResolver authenticationRequestResolver() {	OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);	resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest()            .setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"));	return resolver;}----====[NOTE]====Since Spring Security only supports the `POST` binding for authentication, there is not very much value in overriding the protocol binding at this point in time.====== Use the latest `Saml2AuthenticationToken` constructorIn an early release, `Saml2AuthenticationToken` took several individual settings as constructor parameters.This created a challenge each time a new parameter needed to be added.Since most of these settings were part of `RelyingPartyRegistration`, a new constructor was added where a `RelyingPartyRegistration` could be provided, making the constructor more stable.It also is valuable in that it more closely aligns with the design of `OAuth2LoginAuthenticationToken`.Most applications do not construct this class directly since `Saml2WebSsoAuthenticationFilter` does.However, in the event that your application constructs one, please change from:====.Java[source,java,role="primary"]----new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),    registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())----.Kotlin[source,kotlin,role="secondary"]----Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),    registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())----====to:====.Java[source,java,role="primary"]----new Saml2AuthenticationToken(saml2Response, registration)----.Kotlin[source,kotlin,role="secondary"]----Saml2AuthenticationToken(saml2Response, registration)----====== Use `RelyingPartyRegistration` updated methodsIn an early release of Spring Security's SAML support, there was some ambiguity on the meaning of certain `RelyingPartyRegistration` methods and their function.As more capabilities were added to `RelyingPartyRegistration`, it became necessary to clarify this ambiguity by changing method names to ones that aligned with spec language.The deprecated methods in `RelyingPartyRegstration` are removed.To prepare for that, consider the following representative usage of `RelyingPartyRegistration`:====.Java[source,java,role="primary"]----String idpEntityId = registration.getRemoteIdpEntityId();String assertionConsumerServiceUrl = registration.getAssertionConsumerServiceUrlTemplate();String idpWebSsoUrl = registration.getIdpWebSsoUrl();String localEntityId = registration.getLocalEntityIdTemplate();List<Saml2X509Credential> verifying = registration.getCredentials().stream()        .filter(Saml2X509Credential::isSignatureVerficationCredential)        .collect(Collectors.toList());----.Kotlin[source,kotlin,role="secondary"]----val idpEntityId: String = registration.getRemoteIdpEntityId()val assertionConsumerServiceUrl: String = registration.getAssertionConsumerServiceUrlTemplate()val idpWebSsoUrl: String = registration.getIdpWebSsoUrl()val localEntityId: String = registration.getLocalEntityIdTemplate()val verifying: List<Saml2X509Credential> = registration.getCredentials()        .filter(Saml2X509Credential::isSignatureVerficationCredential)----====This should change to:====.Java[source,java,role="primary"]----String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();String assertionConsumerServiceLocation = registration.getAssertionConsumerServiceLocation();String singleSignOnServiceLocation = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();String entityId = registration.getEntityId();List<Saml2X509Credential> verifying = registration.getAssertingPartyDetails().getVerificationX509Credentials();----.Kotlin[source,kotlin,role="secondary"]----val assertingPartyEntityId: String = registration.getAssertingPartyDetails().getEntityId()val assertionConsumerServiceLocation: String = registration.getAssertionConsumerServiceLocation()val singleSignOnServiceLocation: String = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()val entityId: String = registration.getEntityId()val verifying: List<Saml2X509Credential> = registration.getAssertingPartyDetails().getVerificationX509Credentials()----====For a complete listing of all changed methods, please see {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[``RelyingPartyRegistration``'s JavaDoc].
 |