123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- = 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].
|