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 aa957dc4e550..9f87dd42a533 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 @@ -140,6 +140,11 @@ public static class Identityprovider { */ private String entityId; + /** + * Endpoint for discovery-based configuration. + */ + private String metadataUri; + private final Singlesignon singlesignon = new Singlesignon(); private final Verification verification = new Verification(); @@ -152,6 +157,14 @@ public void setEntityId(String entityId) { this.entityId = entityId; } + public String getMetadataUri() { + return this.metadataUri; + } + + public void setMetadataUri(String metadataUri) { + this.metadataUri = metadataUri; + } + @Deprecated @DeprecatedConfigurationProperty(reason = "moved to 'singlesignon.url'") public String getSsoUrl() { 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 327bdd4dd05d..747a0d928b8f 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 @@ -28,6 +28,7 @@ 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; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -37,8 +38,10 @@ import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * {@link Configuration @Configuration} used to map {@link Saml2RelyingPartyProperties} to @@ -64,16 +67,25 @@ private RelyingPartyRegistration asRegistration(Map.Entry } private RelyingPartyRegistration asRegistration(String id, Registration properties) { - boolean signRequest = properties.getIdentityprovider().getSinglesignon().isSignRequest(); - validateSigningCredentials(properties, signRequest); - RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(id); + RelyingPartyRegistration.Builder builder; + boolean usingMetadata = StringUtils.hasText(properties.getIdentityprovider().getMetadataUri()); + if (usingMetadata) { + builder = RelyingPartyRegistrations.fromMetadataLocation(properties.getIdentityprovider().getMetadataUri()) + .registrationId(id); + } + else { + builder = RelyingPartyRegistration.withRegistrationId(id); + } builder.assertionConsumerServiceLocation( "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + Saml2RelyingPartyProperties.Identityprovider identityprovider = properties.getIdentityprovider(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); builder.assertingPartyDetails((details) -> { - details.singleSignOnServiceLocation(properties.getIdentityprovider().getSinglesignon().getUrl()); - details.entityId(properties.getIdentityprovider().getEntityId()); - details.singleSignOnServiceBinding(properties.getIdentityprovider().getSinglesignon().getBinding()); - details.wantAuthnRequestsSigned(signRequest); + map.from(identityprovider::getEntityId).to(details::entityId); + map.from(identityprovider.getSinglesignon()::getBinding).to(details::singleSignOnServiceBinding); + map.from(identityprovider.getSinglesignon()::getUrl).to(details::singleSignOnServiceLocation); + map.from(identityprovider.getSinglesignon()::isSignRequest).when((signRequest) -> !usingMetadata) + .to(details::wantAuthnRequestsSigned); }); builder.signingX509Credentials((credentials) -> properties.getSigning().getCredentials().stream() .map(this::asSigningCredential).forEach(credentials::add)); @@ -81,7 +93,10 @@ private RelyingPartyRegistration asRegistration(String id, Registration properti .verificationX509Credentials((credentials) -> properties.getIdentityprovider().getVerification() .getCredentials().stream().map(this::asVerificationCredential).forEach(credentials::add))); builder.entityId(properties.getRelyingPartyEntityId()); - return builder.build(); + RelyingPartyRegistration registration = builder.build(); + boolean signRequest = registration.getAssertingPartyDetails().getWantAuthnRequestsSigned(); + validateSigningCredentials(properties, signRequest); + return registration; } private void validateSigningCredentials(Registration properties, boolean signRequest) { 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 7557912d4805..ccb9e1d94817 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 @@ -16,10 +16,14 @@ package org.springframework.boot.autoconfigure.security.saml2; +import java.io.InputStream; import java.util.List; import javax.servlet.Filter; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okio.Buffer; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -30,6 +34,7 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -112,6 +117,20 @@ void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrow .run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class)); } + @Test + void autoconfigurationShouldQueryIdentityProviderMetadataWhenMetadataUrlIsPresent() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String metadataUrl = server.url("").toString(); + setupMockResponse(server); + this.contextRunner.withPropertyValues(PREFIX + ".foo.identityprovider.metadata-uri=" + metadataUrl) + .run((context) -> { + assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class); + assertThat(server.getRequestCount()).isEqualTo(1); + }); + } + } + @Test void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() { this.contextRunner.withPropertyValues(getPropertyValues()) @@ -176,6 +195,14 @@ private boolean hasFilter(AssertableWebApplicationContext context, Class + + + + + + MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB + BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe + Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t + cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP + ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS + v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN + iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece + byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz + cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v + dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX + gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w + dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW + BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu + 9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL + qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU + duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU + yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p + V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e + Cq53OZt9ISjHEw== + + + + + + + + mailto:technical.contact@example.com + + \ No newline at end of file