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 837307fe35..2f45e4ac3f 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 @@ -20,6 +20,7 @@ import java.io.InputStream; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -54,6 +55,7 @@ import org.springframework.util.StringUtils; * @author Madhura Bhave * @author Phillip Webb * @author Moritz Halbritter + * @author Lasse Lindqvist */ @Configuration(proxyBeanMethods = false) @Conditional(RegistrationConfiguredCondition.class) @@ -76,10 +78,8 @@ class Saml2RelyingPartyRegistrationConfiguration { private RelyingPartyRegistration asRegistration(String id, Registration properties) { boolean usingMetadata = StringUtils.hasText(properties.getAssertingparty().getMetadataUri()); - Builder builder = (usingMetadata) - ? RelyingPartyRegistrations.fromMetadataLocation(properties.getAssertingparty().getMetadataUri()) - .registrationId(id) - : RelyingPartyRegistration.withRegistrationId(id); + Builder builder = (!usingMetadata) ? RelyingPartyRegistration.withRegistrationId(id) + : createBuilderUsingMetadata(id, properties.getAssertingparty()).registrationId(id); builder.assertionConsumerServiceLocation(properties.getAcs().getLocation()); builder.assertionConsumerServiceBinding(properties.getAcs().getBinding()); builder.assertingPartyDetails(mapAssertingParty(properties.getAssertingparty(), usingMetadata)); @@ -110,6 +110,24 @@ class Saml2RelyingPartyRegistrationConfiguration { return registration; } + private RelyingPartyRegistration.Builder createBuilderUsingMetadata(String id, AssertingParty properties) { + String requiredEntityId = properties.getEntityId(); + Collection candidates = RelyingPartyRegistrations + .collectionFromMetadataLocation(properties.getMetadataUri()); + for (RelyingPartyRegistration.Builder candidate : candidates) { + if (requiredEntityId == null || requiredEntityId.equals(getEntityId(candidate))) { + return candidate; + } + } + throw new IllegalStateException("No relying party with Entity ID '" + requiredEntityId + "' found"); + } + + private Object getEntityId(RelyingPartyRegistration.Builder candidate) { + String[] result = new String[1]; + candidate.assertingPartyDetails((builder) -> result[0] = builder.build().getEntityId()); + return result[0]; + } + private Consumer mapAssertingParty(AssertingParty assertingParty, boolean usingMetadata) { return (details) -> { 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 a5a97d113a..d3a2fb361d 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,6 +16,7 @@ package org.springframework.boot.autoconfigure.security.saml2; +import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -55,6 +56,7 @@ import static org.mockito.Mockito.mock; * * @author Madhura Bhave * @author Moritz Halbritter + * @author Lasse Lindqvist */ class Saml2RelyingPartyAutoConfigurationTests { @@ -240,6 +242,39 @@ class Saml2RelyingPartyAutoConfigurationTests { PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:saml/certificate-location" }; } + @Test + void autoconfigurationWhenMultipleProvidersAndNoSpecifiedEntityId() throws Exception { + testMultipleProviders(null, "https://idp.example.com/idp/shibboleth"); + } + + @Test + void autoconfigurationWhenMultipleProvidersAndSpecifiedEntityId() throws Exception { + testMultipleProviders("https://idp.example.com/idp/shibboleth", "https://idp.example.com/idp/shibboleth"); + testMultipleProviders("https://idp2.example.com/idp/shibboleth", "https://idp2.example.com/idp/shibboleth"); + } + + private void testMultipleProviders(String specifiedEntityId, String expected) throws IOException, Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String metadataUrl = server.url("").toString(); + setupMockResponse(server, new ClassPathResource("saml/idp-metadata-with-multiple-providers")); + WebApplicationContextRunner contextRunner = this.contextRunner + .withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl); + if (specifiedEntityId != null) { + contextRunner = contextRunner + .withPropertyValues(PREFIX + ".foo.assertingparty.entity-id=" + specifiedEntityId); + } + contextRunner.run((context) -> { + assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class); + assertThat(server.getRequestCount()).isOne(); + RelyingPartyRegistrationRepository repository = context + .getBean(RelyingPartyRegistrationRepository.class); + RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration.getAssertingPartyDetails().getEntityId()).isEqualTo(expected); + }); + } + } + private String[] getPropertyValuesWithoutSsoBinding() { return new String[] { PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/idp-metadata-with-multiple-providers b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/idp-metadata-with-multiple-providers new file mode 100644 index 0000000000..af40448589 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/idp-metadata-with-multiple-providers @@ -0,0 +1,86 @@ + + + + + + + + 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 + + + + + + + + + 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.contact2@example.com + + + \ No newline at end of file