You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
356 lines
13 KiB
356 lines
13 KiB
= 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: |
|
|
|
[tabs] |
|
====== |
|
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`: |
|
|
|
[tabs] |
|
====== |
|
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: |
|
|
|
[tabs] |
|
====== |
|
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` 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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
Saml2AuthenticationRequestFactory authenticationRequestFactory() { |
|
OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory(); |
|
factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST") |
|
return factory; |
|
} |
|
---- |
|
====== |
|
|
|
you can do: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@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; |
|
} |
|
---- |
|
====== |
|
|
|
[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` 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: |
|
|
|
[tabs] |
|
====== |
|
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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
new Saml2AuthenticationToken(saml2Response, registration) |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
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`: |
|
|
|
[tabs] |
|
====== |
|
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: |
|
|
|
[tabs] |
|
====== |
|
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].
|
|
|