Skip to content

Commit 094cc55

Browse files
committed
Merge pull request #35902 from lasselindqvist
* pr/35902: Polish 'Choose SAML party based on entity ID rather than always using first' Choose SAML party based on entity ID rather than always using first Closes gh-35902
2 parents 51ee702 + b699094 commit 094cc55

File tree

3 files changed

+144
-3
lines changed

3 files changed

+144
-3
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.security.cert.CertificateFactory;
2121
import java.security.cert.X509Certificate;
2222
import java.security.interfaces.RSAPrivateKey;
23+
import java.util.Collection;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.function.Consumer;
@@ -63,6 +64,7 @@
6364
* @author Madhura Bhave
6465
* @author Phillip Webb
6566
* @author Moritz Halbritter
67+
* @author Lasse Lindqvist
6668
*/
6769
@Configuration(proxyBeanMethods = false)
6870
@Conditional(RegistrationConfiguredCondition.class)
@@ -88,9 +90,8 @@ private RelyingPartyRegistration asRegistration(Map.Entry<String, Registration>
8890
private RelyingPartyRegistration asRegistration(String id, Registration properties) {
8991
AssertingPartyProperties assertingParty = new AssertingPartyProperties(properties, id);
9092
boolean usingMetadata = StringUtils.hasText(assertingParty.getMetadataUri());
91-
Builder builder = (usingMetadata)
92-
? RelyingPartyRegistrations.fromMetadataLocation(assertingParty.getMetadataUri()).registrationId(id)
93-
: RelyingPartyRegistration.withRegistrationId(id);
93+
Builder builder = (!usingMetadata) ? RelyingPartyRegistration.withRegistrationId(id)
94+
: createBuilderUsingMetadata(id, assertingParty).registrationId(id);
9495
builder.assertionConsumerServiceLocation(properties.getAcs().getLocation());
9596
builder.assertionConsumerServiceBinding(properties.getAcs().getBinding());
9697
builder.assertingPartyDetails(mapAssertingParty(properties, id, usingMetadata));
@@ -119,6 +120,25 @@ private RelyingPartyRegistration asRegistration(String id, Registration properti
119120
return registration;
120121
}
121122

123+
private RelyingPartyRegistration.Builder createBuilderUsingMetadata(String id,
124+
AssertingPartyProperties properties) {
125+
String requiredEntityId = properties.getEntityId();
126+
Collection<Builder> candidates = RelyingPartyRegistrations
127+
.collectionFromMetadataLocation(properties.getMetadataUri());
128+
for (RelyingPartyRegistration.Builder candidate : candidates) {
129+
if (requiredEntityId == null || requiredEntityId.equals(getEntityId(candidate))) {
130+
return candidate;
131+
}
132+
}
133+
throw new IllegalStateException("No relying party with Entity ID '" + requiredEntityId + "' found");
134+
}
135+
136+
private Object getEntityId(RelyingPartyRegistration.Builder candidate) {
137+
String[] result = new String[1];
138+
candidate.assertingPartyDetails((builder) -> result[0] = builder.build().getEntityId());
139+
return result[0];
140+
}
141+
122142
private Consumer<AssertingPartyDetails.Builder> mapAssertingParty(Registration registration, String id,
123143
boolean usingMetadata) {
124144
return (details) -> {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.autoconfigure.security.saml2;
1818

19+
import java.io.IOException;
1920
import java.io.InputStream;
2021
import java.util.List;
2122

@@ -55,6 +56,7 @@
5556
*
5657
* @author Madhura Bhave
5758
* @author Moritz Halbritter
59+
* @author Lasse Lindqvist
5860
*/
5961
class Saml2RelyingPartyAutoConfigurationTests {
6062

@@ -403,6 +405,39 @@ void samlLogoutShouldBeConfigured() {
403405
.run((context) -> assertThat(hasFilter(context, Saml2LogoutRequestFilter.class)).isTrue());
404406
}
405407

408+
@Test
409+
void autoconfigurationWhenMultipleProvidersAndNoSpecifiedEntityId() throws Exception {
410+
testMultipleProviders(null, "https://idp.example.com/idp/shibboleth");
411+
}
412+
413+
@Test
414+
void autoconfigurationWhenMultipleProvidersAndSpecifiedEntityId() throws Exception {
415+
testMultipleProviders("https://idp.example.com/idp/shibboleth", "https://idp.example.com/idp/shibboleth");
416+
testMultipleProviders("https://idp2.example.com/idp/shibboleth", "https://idp2.example.com/idp/shibboleth");
417+
}
418+
419+
private void testMultipleProviders(String specifiedEntityId, String expected) throws IOException, Exception {
420+
try (MockWebServer server = new MockWebServer()) {
421+
server.start();
422+
String metadataUrl = server.url("").toString();
423+
setupMockResponse(server, new ClassPathResource("saml/idp-metadata-with-multiple-providers"));
424+
WebApplicationContextRunner contextRunner = this.contextRunner
425+
.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl);
426+
if (specifiedEntityId != null) {
427+
contextRunner = contextRunner
428+
.withPropertyValues(PREFIX + ".foo.assertingparty.entity-id=" + specifiedEntityId);
429+
}
430+
contextRunner.run((context) -> {
431+
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
432+
assertThat(server.getRequestCount()).isOne();
433+
RelyingPartyRegistrationRepository repository = context
434+
.getBean(RelyingPartyRegistrationRepository.class);
435+
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
436+
assertThat(registration.getAssertingPartyDetails().getEntityId()).isEqualTo(expected);
437+
});
438+
}
439+
}
440+
406441
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests, boolean useDeprecated) {
407442
String assertingParty = useDeprecated ? "identityprovider" : "assertingparty";
408443
return new String[] {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="virtu-20230614094100" Name="virtu" validUntil="2023-07-12T06:41:00Z" xsi:schemaLocation="urn:oasis:names:tc:SAML:2.0:metadata saml-schema-metadata-2.0.xsd http://www.w3.org/2000/09/xmldsig# xmldsig-core-schema.xsd">
2+
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
3+
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
6+
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
7+
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
8+
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
9+
<md:KeyDescriptor>
10+
<ds:KeyInfo>
11+
<ds:X509Data>
12+
<ds:X509Certificate>
13+
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
14+
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
15+
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
16+
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
17+
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
18+
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
19+
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
20+
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
21+
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
22+
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
23+
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
24+
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
25+
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
26+
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
27+
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
28+
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
29+
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
30+
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
31+
Cq53OZt9ISjHEw==
32+
</ds:X509Certificate>
33+
</ds:X509Data>
34+
</ds:KeyInfo>
35+
</md:KeyDescriptor>
36+
<md:SingleSignOnService
37+
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
38+
Location="https://idp.example.com/sso"/>
39+
</md:IDPSSODescriptor>
40+
<md:ContactPerson contactType="technical">
41+
<md:EmailAddress>mailto:[email protected]</md:EmailAddress>
42+
</md:ContactPerson>
43+
</md:EntityDescriptor>
44+
<md:EntityDescriptor entityID="https://idp2.example.com/idp/shibboleth"
45+
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
46+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
47+
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
48+
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
49+
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
50+
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
51+
<md:KeyDescriptor>
52+
<ds:KeyInfo>
53+
<ds:X509Data>
54+
<ds:X509Certificate>
55+
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
56+
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
57+
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
58+
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
59+
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
60+
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
61+
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
62+
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
63+
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
64+
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
65+
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
66+
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
67+
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
68+
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
69+
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
70+
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
71+
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
72+
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
73+
Cq53OZt9ISjHEw==
74+
</ds:X509Certificate>
75+
</ds:X509Data>
76+
</ds:KeyInfo>
77+
</md:KeyDescriptor>
78+
<md:SingleSignOnService
79+
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
80+
Location="https://idp2.example.com/sso"/>
81+
</md:IDPSSODescriptor>
82+
<md:ContactPerson contactType="technical">
83+
<md:EmailAddress>mailto:[email protected]</md:EmailAddress>
84+
</md:ContactPerson>
85+
</md:EntityDescriptor>
86+
</EntitiesDescriptor>

0 commit comments

Comments
 (0)