From 6c400daa489caa0987de1487c09ee784d310d08c Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 14 Apr 2022 11:27:05 +0200 Subject: [PATCH] Rename 'identityprovider' property to 'asserting-party' Rename spring.security.saml2.relyingparty.registration.*.identity-provider.* to spring.security.saml2.relyingparty.registration.*.asserting-party.* The old property names are still supported, but will lead to a warning in the logs. Closes gh-30642 --- ...mentWebSecurityAutoConfigurationTests.java | 17 +- .../saml2/Saml2RelyingPartyProperties.java | 33 ++- ...RelyingPartyRegistrationConfiguration.java | 58 +++-- ...ml2RelyingPartyAutoConfigurationTests.java | 236 +++++++++++++++--- .../Saml2RelyingPartyPropertiesTests.java | 29 +-- .../docs/asciidoc/web/spring-security.adoc | 4 +- .../src/main/resources/application.yml | 8 +- ...mpleSaml2RelyingPartyApplicationTests.java | 2 +- 8 files changed, 308 insertions(+), 79 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java index d4b067c900..dbe6167992 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java @@ -136,8 +136,21 @@ class ManagementWebSecurityAutoConfigurationTests { void backOffIfSaml2RelyingPartyAutoConfigurationPresent() { this.contextRunner.withConfiguration(AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class)) .withPropertyValues( - "spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.url=https://simplesaml-for-spring-saml/SSOService.php", - "spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.sign-request=false", + "spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.single-sign-on.url=https://simplesaml-for-spring-saml/SSOService.php", + "spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.single-sign-on.sign-request=false", + "spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + "spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.verification.credentials[0].certificate-location=classpath:saml/certificate-location") + .run((context) -> assertThat(context).doesNotHaveBean(ManagementWebSecurityAutoConfiguration.class) + .doesNotHaveBean(MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN)); + } + + @Test + @Deprecated + void backOffIfSaml2RelyingPartyAutoConfigurationPresentDeprecated() { + this.contextRunner.withConfiguration(AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class)) + .withPropertyValues( + "spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.single-sign-on.url=https://simplesaml-for-spring-saml/SSOService.php", + "spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.single-sign-on.sign-request=false", "spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", "spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location") .run((context) -> assertThat(context).doesNotHaveBean(ManagementWebSecurityAutoConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java index cd85b0ede8..73d74fdf9d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,14 @@ public class Saml2RelyingPartyProperties { /** * Remote SAML Identity Provider. */ - private final Identityprovider identityprovider = new Identityprovider(); + private final AssertingParty assertingParty = new AssertingParty(); + + /** + * Remote SAML Identity Provider. + * @deprecated use {@link #assertingParty} + */ + @Deprecated + private final AssertingParty identityprovider = new AssertingParty(); public String getEntityId() { return this.entityId; @@ -89,7 +96,17 @@ public class Saml2RelyingPartyProperties { return this.decryption; } - public Identityprovider getIdentityprovider() { + public AssertingParty getAssertingParty() { + return this.assertingParty; + } + + /** + * Remote SAML Identity Provider. + * @return remote SAML Identity Provider + * @deprecated use {@link #getAssertingParty()} + */ + @Deprecated + public AssertingParty getIdentityprovider() { return this.identityprovider; } @@ -224,7 +241,7 @@ public class Saml2RelyingPartyProperties { /** * Represents a remote Identity Provider. */ - public static class Identityprovider { + public static class AssertingParty { /** * Unique identifier for the identity provider. @@ -282,7 +299,7 @@ public class Saml2RelyingPartyProperties { /** * Whether to sign authentication requests. */ - private boolean signRequest = true; + private Boolean signRequest; public String getUrl() { return this.url; @@ -304,7 +321,11 @@ public class Saml2RelyingPartyProperties { return this.signRequest; } - public void setSignRequest(boolean signRequest) { + public Boolean getSignRequest() { + return this.signRequest; + } + + public void setSignRequest(Boolean signRequest) { this.signRequest = signRequest; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java index f795bae223..66c2a5473a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,16 @@ import java.security.interfaces.RSAPrivateKey; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.AssertingParty; +import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.AssertingParty.Verification; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Decryption; -import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Identityprovider.Verification; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration.Signing; import org.springframework.boot.context.properties.PropertyMapper; @@ -59,6 +64,8 @@ import org.springframework.util.StringUtils; @ConditionalOnMissingBean(RelyingPartyRegistrationRepository.class) class Saml2RelyingPartyRegistrationConfiguration { + private static final Log logger = LogFactory.getLog(Saml2RelyingPartyRegistrationConfiguration.class); + @Bean RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(Saml2RelyingPartyProperties properties) { List registrations = properties.getRegistration().entrySet().stream() @@ -71,19 +78,21 @@ class Saml2RelyingPartyRegistrationConfiguration { } private RelyingPartyRegistration asRegistration(String id, Registration properties) { - boolean usingMetadata = StringUtils.hasText(properties.getIdentityprovider().getMetadataUri()); + boolean usingMetadata = StringUtils + .hasText(getFromAssertingParty(properties, id, "metadata-uri", AssertingParty::getMetadataUri)); Builder builder = (usingMetadata) ? RelyingPartyRegistrations - .fromMetadataLocation(properties.getIdentityprovider().getMetadataUri()).registrationId(id) - : RelyingPartyRegistration.withRegistrationId(id); + .fromMetadataLocation( + getFromAssertingParty(properties, id, "metadata-uri", AssertingParty::getMetadataUri)) + .registrationId(id) : RelyingPartyRegistration.withRegistrationId(id); builder.assertionConsumerServiceLocation(properties.getAcs().getLocation()); builder.assertionConsumerServiceBinding(properties.getAcs().getBinding()); - builder.assertingPartyDetails(mapIdentityProvider(properties, usingMetadata)); + builder.assertingPartyDetails(mapAssertingParty(properties, id, usingMetadata)); builder.signingX509Credentials((credentials) -> properties.getSigning().getCredentials().stream() .map(this::asSigningCredential).forEach(credentials::add)); builder.decryptionX509Credentials((credentials) -> properties.getDecryption().getCredentials().stream() .map(this::asDecryptionCredential).forEach(credentials::add)); - builder.assertingPartyDetails((details) -> details - .verificationX509Credentials((credentials) -> properties.getIdentityprovider().getVerification() + builder.assertingPartyDetails((details) -> details.verificationX509Credentials( + (credentials) -> getFromAssertingParty(properties, id, "verification", AssertingParty::getVerification) .getCredentials().stream().map(this::asVerificationCredential).forEach(credentials::add))); builder.entityId(properties.getEntityId()); RelyingPartyRegistration registration = builder.build(); @@ -92,16 +101,35 @@ class Saml2RelyingPartyRegistrationConfiguration { return registration; } - private Consumer mapIdentityProvider(Registration properties, + @SuppressWarnings("deprecation") + private T getFromAssertingParty(Registration registration, String id, String name, + Function getter) { + T newValue = getter.apply(registration.getAssertingParty()); + if (newValue != null) { + return newValue; + } + T deprecatedValue = getter.apply(registration.getIdentityprovider()); + if (deprecatedValue != null) { + logger.warn(String.format( + "Property 'spring.security.saml2.relyingparty.registration.identityprovider.%1$s.%2$s' is deprecated, please use 'spring.security.saml2.relyingparty.registration.asserting-party.%1$s.%2$s' instead", + id, name)); + return deprecatedValue; + } + return newValue; + } + + private Consumer mapAssertingParty(Registration registration, String id, boolean usingMetadata) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - Saml2RelyingPartyProperties.Identityprovider identityprovider = properties.getIdentityprovider(); return (details) -> { - map.from(identityprovider::getEntityId).to(details::entityId); - map.from(identityprovider.getSinglesignon()::getBinding).whenNonNull() - .to(details::singleSignOnServiceBinding); - map.from(identityprovider.getSinglesignon()::getUrl).to(details::singleSignOnServiceLocation); - map.from(identityprovider.getSinglesignon()::isSignRequest).when((signRequest) -> !usingMetadata) + map.from(() -> getFromAssertingParty(registration, id, "entity-id", AssertingParty::getEntityId)) + .to(details::entityId); + map.from(() -> getFromAssertingParty(registration, id, "singlesignon.binding", + (property) -> property.getSinglesignon().getBinding())).to(details::singleSignOnServiceBinding); + map.from(() -> getFromAssertingParty(registration, id, "singlesignon.url", + (property) -> property.getSinglesignon().getUrl())).to(details::singleSignOnServiceLocation); + map.from(() -> getFromAssertingParty(registration, id, "singlesignon.sign-request", + (property) -> property.getSinglesignon().getSignRequest())).when((ignored) -> !usingMetadata) .to(details::wantAuthnRequestsSigned); }; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java index c939eb810a..03a1306d7d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java @@ -63,7 +63,15 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void autoConfigurationShouldBeConditionalOnRelyingPartyRegistrationRepositoryClass() { - this.contextRunner.withPropertyValues(getPropertyValues()).withClassLoader(new FilteredClassLoader( + this.contextRunner.withPropertyValues(getPropertyValues(false)).withClassLoader(new FilteredClassLoader( + "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository")) + .run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class)); + } + + @Test + @Deprecated + void autoConfigurationShouldBeConditionalOnRelyingPartyRegistrationRepositoryClassDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValues(true)).withClassLoader(new FilteredClassLoader( "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository")) .run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class)); } @@ -72,7 +80,16 @@ class Saml2RelyingPartyAutoConfigurationTests { void autoConfigurationShouldBeConditionalOnServletWebApplication() { new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class)) - .withPropertyValues(getPropertyValues()) + .withPropertyValues(getPropertyValues(false)) + .run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class)); + } + + @Test + @Deprecated + void autoConfigurationShouldBeConditionalOnServletWebApplicationDeprecated() { + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class)) + .withPropertyValues(getPropertyValues(true)) .run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class)); } @@ -84,7 +101,31 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() { - this.contextRunner.withPropertyValues(getPropertyValues()).run((context) -> { + this.contextRunner.withPropertyValues(getPropertyValues(false)).run((context) -> { + RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); + RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); + + assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()) + .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"); + assertThat(registration.getAssertingPartyDetails().getEntityId()) + .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"); + assertThat(registration.getAssertionConsumerServiceLocation()) + .isEqualTo("{baseUrl}/login/saml2/foo-entity-id"); + assertThat(registration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); + assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + .isEqualTo(Saml2MessageBinding.POST); + assertThat(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()).isEqualTo(false); + assertThat(registration.getSigningX509Credentials()).hasSize(1); + assertThat(registration.getDecryptionX509Credentials()).hasSize(1); + assertThat(registration.getAssertingPartyDetails().getVerificationX509Credentials()).isNotNull(); + assertThat(registration.getEntityId()).isEqualTo("{baseUrl}/saml2/foo-entity-id"); + }); + } + + @Test + @Deprecated + void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresentDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValues(true)).run((context) -> { RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); @@ -107,7 +148,18 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void autoConfigurationWhenSignRequestsTrueAndNoSigningCredentialsShouldThrowException() { - this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(true)).run((context) -> { + this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(true, false)) + .run((context) -> { + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure()).hasMessageContaining( + "Signing credentials must not be empty when authentication requests require signing."); + }); + } + + @Test + @Deprecated + void autoConfigurationWhenSignRequestsTrueAndNoSigningCredentialsShouldThrowExceptionDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(true, true)).run((context) -> { assertThat(context).hasFailed(); assertThat(context.getStartupFailure()).hasMessageContaining( "Signing credentials must not be empty when authentication requests require signing."); @@ -116,12 +168,34 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrowException() { - this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false)) + this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false, false)) .run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class)); } @Test - void autoconfigurationShouldQueryIdentityProviderMetadataWhenMetadataUrlIsPresent() throws Exception { + @Deprecated + void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrowExceptionDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false, true)) + .run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class)); + } + + @Test + void autoconfigurationShouldQueryAssertingPartyMetadataWhenMetadataUrlIsPresent() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String metadataUrl = server.url("").toString(); + setupMockResponse(server, new ClassPathResource("saml/idp-metadata")); + this.contextRunner.withPropertyValues(PREFIX + ".foo.asserting-party.metadata-uri=" + metadataUrl) + .run((context) -> { + assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class); + assertThat(server.getRequestCount()).isEqualTo(1); + }); + } + } + + @Test + @Deprecated + void autoconfigurationShouldQueryAssertingPartyMetadataWhenMetadataUrlIsPresentDeprecated() throws Exception { try (MockWebServer server = new MockWebServer()) { server.start(); String metadataUrl = server.url("").toString(); @@ -136,6 +210,24 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void autoconfigurationShouldUseBindingFromMetadataUrlIfPresent() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String metadataUrl = server.url("").toString(); + setupMockResponse(server, new ClassPathResource("saml/idp-metadata")); + this.contextRunner.withPropertyValues(PREFIX + ".foo.asserting-party.metadata-uri=" + metadataUrl) + .run((context) -> { + RelyingPartyRegistrationRepository repository = context + .getBean(RelyingPartyRegistrationRepository.class); + RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + .isEqualTo(Saml2MessageBinding.POST); + }); + } + } + + @Test + @Deprecated + void autoconfigurationShouldUseBindingFromMetadataUrlIfPresentDeprecated() throws Exception { try (MockWebServer server = new MockWebServer()) { server.start(); String metadataUrl = server.url("").toString(); @@ -153,6 +245,24 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void autoconfigurationWhenMetadataUrlAndPropertyPresentShouldUseBindingFromProperty() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String metadataUrl = server.url("").toString(); + setupMockResponse(server, new ClassPathResource("saml/idp-metadata")); + this.contextRunner.withPropertyValues(PREFIX + ".foo.asserting-party.metadata-uri=" + metadataUrl, + PREFIX + ".foo.asserting-party.singlesignon.binding=redirect").run((context) -> { + RelyingPartyRegistrationRepository repository = context + .getBean(RelyingPartyRegistrationRepository.class); + RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + .isEqualTo(Saml2MessageBinding.REDIRECT); + }); + } + } + + @Test + @Deprecated + void autoconfigurationWhenMetadataUrlAndPropertyPresentShouldUseBindingFromPropertyDeprecated() throws Exception { try (MockWebServer server = new MockWebServer()) { server.start(); String metadataUrl = server.url("").toString(); @@ -170,7 +280,18 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void autoconfigurationWhenNoMetadataUrlOrPropertyPresentShouldUseRedirectBinding() { - this.contextRunner.withPropertyValues(getPropertyValuesWithoutSsoBinding()).run((context) -> { + this.contextRunner.withPropertyValues(getPropertyValuesWithoutSsoBinding(false)).run((context) -> { + RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); + RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + .isEqualTo(Saml2MessageBinding.REDIRECT); + }); + } + + @Test + @Deprecated + void autoconfigurationWhenNoMetadataUrlOrPropertyPresentShouldUseRedirectBindingDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValuesWithoutSsoBinding(true)).run((context) -> { RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); assertThat(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) @@ -180,7 +301,17 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() { - this.contextRunner.withPropertyValues(getPropertyValues()) + this.contextRunner.withPropertyValues(getPropertyValues(false)) + .withUserConfiguration(RegistrationRepositoryConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class); + assertThat(context).hasBean("testRegistrationRepository"); + }); + } + + @Test + @Deprecated + void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBeanDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValues(true)) .withUserConfiguration(RegistrationRepositoryConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class); assertThat(context).hasBean("testRegistrationRepository"); @@ -189,59 +320,102 @@ class Saml2RelyingPartyAutoConfigurationTests { @Test void samlLoginShouldBeConfigured() { - this.contextRunner.withPropertyValues(getPropertyValues()) + this.contextRunner.withPropertyValues(getPropertyValues(false)) + .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue()); + } + + @Test + @Deprecated + void samlLoginShouldBeConfiguredDeprecated() { + this.contextRunner.withPropertyValues(getPropertyValues(true)) .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue()); } @Test void samlLoginShouldBackOffWhenAWebSecurityConfigurerAdapterIsDefined() { this.contextRunner.withUserConfiguration(WebSecurityConfigurerAdapterConfiguration.class) - .withPropertyValues(getPropertyValues()) + .withPropertyValues(getPropertyValues(false)) + .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse()); + } + + @Test + @Deprecated + void samlLoginShouldBackOffWhenAWebSecurityConfigurerAdapterIsDefinedDeprecated() { + this.contextRunner.withUserConfiguration(WebSecurityConfigurerAdapterConfiguration.class) + .withPropertyValues(getPropertyValues(true)) .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse()); } @Test void samlLoginShouldBackOffWhenASecurityFilterChainBeanIsPresent() { this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class) - .withPropertyValues(getPropertyValues()) + .withPropertyValues(getPropertyValues(false)) + .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse()); + } + + @Test + @Deprecated + void samlLoginShouldBackOffWhenASecurityFilterChainBeanIsPresentDeprecated() { + this.contextRunner.withUserConfiguration(TestSecurityFilterChainConfig.class) + .withPropertyValues(getPropertyValues(true)) .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse()); } @Test void samlLoginShouldShouldBeConditionalOnSecurityWebFilterClass() { this.contextRunner.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)) - .withPropertyValues(getPropertyValues()) + .withPropertyValues(getPropertyValues(false)) .run((context) -> assertThat(context).doesNotHaveBean(SecurityFilterChain.class)); } - private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) { - return new String[] { PREFIX - + ".foo.identityprovider.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", - PREFIX + ".foo.identityprovider.singlesignon.binding=post", - PREFIX + ".foo.identityprovider.singlesignon.sign-request=" + signRequests, - PREFIX + ".foo.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", - PREFIX + ".foo.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location" }; + @Test + @Deprecated + void samlLoginShouldShouldBeConditionalOnSecurityWebFilterClassDeprecated() { + this.contextRunner.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)) + .withPropertyValues(getPropertyValues(true)) + .run((context) -> assertThat(context).doesNotHaveBean(SecurityFilterChain.class)); } - private String[] getPropertyValuesWithoutSsoBinding() { - return new String[] { PREFIX - + ".foo.identityprovider.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", - PREFIX + ".foo.identityprovider.singlesignon.sign-request=false", - PREFIX + ".foo.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", - PREFIX + ".foo.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location" }; + private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests, boolean useDeprecated) { + String assertingParty = useDeprecated ? "identityprovider" : "asserting-party"; + return new String[] { + PREFIX + ".foo." + assertingParty + + ".singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + PREFIX + ".foo." + assertingParty + ".singlesignon.binding=post", + PREFIX + ".foo." + assertingParty + ".singlesignon.sign-request=" + signRequests, + PREFIX + ".foo." + assertingParty + + ".entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + PREFIX + ".foo." + assertingParty + + ".verification.credentials[0].certificate-location=classpath:saml/certificate-location" }; + } + + private String[] getPropertyValuesWithoutSsoBinding(boolean useDeprecated) { + String assertingParty = useDeprecated ? "identityprovider" : "asserting-party"; + return new String[] { + PREFIX + ".foo." + assertingParty + + ".singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + PREFIX + ".foo." + assertingParty + ".singlesignon.sign-request=false", + PREFIX + ".foo." + assertingParty + + ".entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + PREFIX + ".foo." + assertingParty + + ".verification.credentials[0].certificate-location=classpath:saml/certificate-location" }; } - private String[] getPropertyValues() { + private String[] getPropertyValues(boolean useDeprecated) { + String assertingParty = useDeprecated ? "identityprovider" : "asserting-party"; return new String[] { PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/private-key-location", PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/certificate-location", PREFIX + ".foo.decryption.credentials[0].private-key-location=classpath:saml/private-key-location", PREFIX + ".foo.decryption.credentials[0].certificate-location=classpath:saml/certificate-location", - PREFIX + ".foo.identityprovider.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", - PREFIX + ".foo.identityprovider.singlesignon.binding=post", - PREFIX + ".foo.identityprovider.singlesignon.sign-request=false", - PREFIX + ".foo.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", - PREFIX + ".foo.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location", + PREFIX + ".foo." + assertingParty + + ".singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + PREFIX + ".foo." + assertingParty + ".singlesignon.binding=post", + PREFIX + ".foo." + assertingParty + ".singlesignon.sign-request=false", + PREFIX + ".foo." + assertingParty + + ".entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + PREFIX + ".foo." + assertingParty + + ".verification.credentials[0].certificate-location=classpath:saml/certificate-location", PREFIX + ".foo.entity-id={baseUrl}/saml2/foo-entity-id", PREFIX + ".foo.acs.location={baseUrl}/login/saml2/foo-entity-id", PREFIX + ".foo.acs.binding=redirect" }; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java index 04f16e9f96..e395e7d639 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,34 +41,27 @@ class Saml2RelyingPartyPropertiesTests { @Test void customizeSsoUrl() { - bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.url", + bind("spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.single-sign-on.url", "https://simplesaml-for-spring-saml/SSOService.php"); assertThat( - this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSinglesignon().getUrl()) + this.properties.getRegistration().get("simplesamlphp").getAssertingParty().getSinglesignon().getUrl()) .isEqualTo("https://simplesaml-for-spring-saml/SSOService.php"); } @Test void customizeSsoBinding() { - bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.binding", + bind("spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.single-sign-on.binding", "post"); - assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSinglesignon() + assertThat(this.properties.getRegistration().get("simplesamlphp").getAssertingParty().getSinglesignon() .getBinding()).isEqualTo(Saml2MessageBinding.POST); } @Test void customizeSsoSignRequests() { - bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.sign-request", + bind("spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.single-sign-on.sign-request", "false"); - assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSinglesignon() - .isSignRequest()).isEqualTo(false); - } - - @Test - void customizeSsoSignRequestsIsTrueByDefault() { - this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration()); - assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSinglesignon() - .isSignRequest()).isEqualTo(true); + assertThat(this.properties.getRegistration().get("simplesamlphp").getAssertingParty().getSinglesignon() + .getSignRequest()).isEqualTo(false); } @Test @@ -86,10 +79,10 @@ class Saml2RelyingPartyPropertiesTests { } @Test - void customizeIdentityProviderMetadataUri() { - bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identityprovider.metadata-uri", + void customizeAssertingPartyMetadataUri() { + bind("spring.security.saml2.relyingparty.registration.simplesamlphp.asserting-party.metadata-uri", "https://idp.example.org/metadata"); - assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getMetadataUri()) + assertThat(this.properties.getRegistration().get("simplesamlphp").getAssertingParty().getMetadataUri()) .isEqualTo("https://idp.example.org/metadata"); } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc index a2370710e7..c7c95443d1 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc @@ -262,7 +262,7 @@ You can register multiple relying parties under the `spring.security.saml2.relyi credentials: - private-key-location: "path-to-private-key" certificate-location: "path-to-certificate" - identityprovider: + asserting-party: verification: credentials: - certificate-location: "path-to-verification-cert" @@ -278,7 +278,7 @@ You can register multiple relying parties under the `spring.security.saml2.relyi credentials: - private-key-location: "path-to-private-key" certificate-location: "path-to-certificate" - identityprovider: + asserting-party: verification: credentials: - certificate-location: "path-to-other-verification-cert" diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml index 4256d2d331..83ac8e90b3 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml @@ -8,10 +8,10 @@ spring: credentials: - private-key-location: "classpath:saml/privatekey.txt" certificate-location: "classpath:saml/certificate.txt" - identityprovider: + asserting-party: verification: credentials: - - certificate-location: "classpath:saml/certificate.txt" + - certificate-location: "classpath:saml/certificate.txt" entity-id: simplesaml singlesignon: url: https://simplesaml-for-spring-saml/SSOService.php @@ -21,10 +21,10 @@ spring: credentials: - private-key-location: "classpath:saml/privatekey.txt" certificate-location: "classpath:saml/certificate.txt" - identityprovider: + asserting-party: verification: credentials: - - certificate-location: "classpath:saml/certificate.txt" + - certificate-location: "classpath:saml/certificate.txt" entity-id: okta-id-1234 singlesignon: url: diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java index 982f20edf0..7250bc1afc 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java @@ -46,7 +46,7 @@ class SampleSaml2RelyingPartyApplicationTests { } @Test - void loginShouldHaveAllIdentityProvidersToChooseFrom() { + void loginShouldHaveAllAssertingPartiesToChooseFrom() { ResponseEntity entity = this.restTemplate.getForEntity("/login", String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getBody()).contains("/saml2/authenticate/simplesamlphp");