This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.4.1! |
SAML Migrations
The following steps relate to changes around how to configure SAML 2.0.
Use OpenSAML 4
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:
-
Maven
-
Gradle
<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>
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
-
Kotlin
OpenSamlAuthenticationProvider versionThree = new OpenSamlAuthenticationProvider();
versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor);
versionThree.setResponseTimeValidationSkew(myDuration);
val versionThree: OpenSamlAuthenticationProvider = OpenSamlAuthenticationProvider()
versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor)
versionThree.setResponseTimeValidationSkew(myDuration)
This should change to:
-
Java
-
Kotlin
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)
Stop Using SAML 2.0 Converter
constructors
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
.
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
@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;
}
to ensure that ForceAuthn is set to true
, you can instead do:
-
Java
@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:
-
Java
@Bean
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
return factory;
}
you can do:
-
Java
@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;
}
Since Spring Security only supports the |
Use the latest Saml2AuthenticationToken
constructor
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:
-
Java
-
Kotlin
new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
to:
-
Java
-
Kotlin
new Saml2AuthenticationToken(saml2Response, registration)
Saml2AuthenticationToken(saml2Response, registration)
Use RelyingPartyRegistration
updated methods
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
:
-
Java
-
Kotlin
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)
This should change to:
-
Java
-
Kotlin
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()
For a complete listing of all changed methods, please see RelyingPartyRegistration
's JavaDoc.