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

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

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 :

OpenSamlAuthenticationProvider versionThree = new OpenSamlAuthenticationProvider();
versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor);
versionThree.setResponseTimeValidationSkew(myDuration);
val versionThree: OpenSamlAuthenticationProvider = OpenSamlAuthenticationProvider()
versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor)
versionThree.setResponseTimeValidationSkew(myDuration)
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);
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)

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

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:

@Bean
Saml2AuthenticationRequestFactory 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;
@Bean
Saml2AuthenticationRequestResolver 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:

@Bean
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
    OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
	factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
	return factory;
@Bean
Saml2AuthenticationRequestResolver authenticationRequestResolver() {
	OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
	resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest()
            .setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"));
	return resolver;

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

new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
    registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())

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

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());
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)
String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
String assertionConsumerServiceLocation = registration.getAssertionConsumerServiceLocation();
String singleSignOnServiceLocation = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
String entityId = registration.getEntityId();
List<Saml2X509Credential> verifying = registration.getAssertingPartyDetails().getVerificationX509Credentials();
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()

Apache®, Apache Tomcat®, Apache Kafka®, Apache Cassandra™, and Apache Geode™ are trademarks or registered trademarks of the Apache Software Foundation in the United States and/or other countries. Java™, Java™ SE, Java™ EE, and OpenJDK™ are trademarks of Oracle and/or its affiliates. Kubernetes® is a registered trademark of the Linux Foundation in the United States and other countries. Linux® is the registered trademark of Linus Torvalds in the United States and other countries. Windows® and Microsoft® Azure are registered trademarks of Microsoft Corporation. “AWS” and “Amazon Web Services” are trademarks or registered trademarks of Amazon.com Inc. or its affiliates. All other trademarks and copyrights are property of their respective owners and are only mentioned for informative purposes. Other names may be trademarks of their respective owners.