Skip to content

Commit 1be596b

Browse files
committed
Use OpenSAML API for registration
Issue gh-11658
1 parent 78a0173 commit 1be596b

File tree

9 files changed

+1285
-144
lines changed

9 files changed

+1285
-144
lines changed

docs/modules/ROOT/pages/servlet/saml2/metadata.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ This allows three valuable features:
4242
* Implementations of `RelyingPartyRegistrationRepository` can more easily articulate a relationship between a relying party and its one or many corresponding asserting parties
4343
* Implementations can verify metadata signatures
4444

45-
For example, `OpenSamlAssertingPartyMetadataRepository` uses OpenSAML's `MetadataResolver`, and API whose implementations regularly refresh the underlying metadata in an expiry-aware fashion.
45+
For example, `OpenSaml4AssertingPartyMetadataRepository` uses OpenSAML's `MetadataResolver`, and API whose implementations regularly refresh the underlying metadata in an expiry-aware fashion.
4646

4747
This means that you can now create a refreshable `RelyingPartyRegistrationRepository` in just a few lines of code:
4848

@@ -119,11 +119,11 @@ class RefreshableRelyingPartyRegistrationRepository : IterableRelyingPartyRegist
119119
======
120120

121121
[TIP]
122-
`OpenSamlAssertingPartyMetadataRepository` also ships with a constructor so you can provide a custom `MetadataResolver`. Since the underlying `MetadataResolver` is doing the expirying and refreshing, if you use the constructor directly, you will only get these features by providing an implementation that does so.
122+
`OpenSaml4AssertingPartyMetadataRepository` also ships with a constructor so you can provide a custom `MetadataResolver`. Since the underlying `MetadataResolver` is doing the expirying and refreshing, if you use the constructor directly, you will only get these features by providing an implementation that does so.
123123

124124
=== Verifying Metadata Signatures
125125

126-
You can also verify metadata signatures using `OpenSamlAssertingPartyMetadataRepository` by providing the appropriate set of ``Saml2X509Credential``s as follows:
126+
You can also verify metadata signatures using `OpenSaml4AssertingPartyMetadataRepository` by providing the appropriate set of ``Saml2X509Credential``s as follows:
127127

128128
[tabs]
129129
======

saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ sourceSets.configureEach { set ->
4343
filter { line -> line.replaceAll(".saml2.internal", ".saml2.provider.service.web") }
4444
with from
4545
}
46+
47+
copy {
48+
into "$projectDir/src/$set.name/java/org/springframework/security/saml2/provider/service/registration"
49+
filter { line -> line.replaceAll(".saml2.internal", ".saml2.provider.service.registration") }
50+
with from
51+
}
4652
}
4753

4854
dependencies {

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/AssertingPartyMetadataRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*
2424
* @author Josh Cummings
2525
* @since 6.4
26-
* @see OpenSamlAssertingPartyMetadataRepository
26+
* @see BaseOpenSamlAssertingPartyMetadataRepository
2727
* @see org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations
2828
*/
2929
public interface AssertingPartyMetadataRepository extends Iterable<AssertingPartyMetadata> {
Lines changed: 41 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@
3030

3131
import javax.annotation.Nonnull;
3232

33-
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
34-
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
35-
import net.shibboleth.utilities.java.support.resolver.ResolverException;
3633
import org.opensaml.core.criterion.EntityIdCriterion;
3734
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
3835
import org.opensaml.saml.criterion.EntityRoleCriterion;
@@ -58,48 +55,34 @@
5855
import org.springframework.lang.Nullable;
5956
import org.springframework.security.saml2.Saml2Exception;
6057
import org.springframework.security.saml2.core.OpenSamlInitializationService;
61-
import org.springframework.security.saml2.core.Saml2X509Credential;
6258
import org.springframework.util.Assert;
6359

64-
/**
65-
* An implementation of {@link AssertingPartyMetadataRepository} that uses a
66-
* {@link MetadataResolver} to retrieve {@link AssertingPartyMetadata} instances.
67-
*
68-
* <p>
69-
* The {@link MetadataResolver} constructed in {@link #withTrustedMetadataLocation}
70-
* provides expiry-aware refreshing.
71-
*
72-
* @author Josh Cummings
73-
* @since 6.4
74-
* @see AssertingPartyMetadataRepository
75-
* @see RelyingPartyRegistrations
76-
*/
77-
public final class OpenSamlAssertingPartyMetadataRepository implements AssertingPartyMetadataRepository {
60+
class BaseOpenSamlAssertingPartyMetadataRepository implements AssertingPartyMetadataRepository {
7861

7962
static {
8063
OpenSamlInitializationService.initialize();
8164
}
8265

83-
private final MetadataResolver metadataResolver;
66+
private final MetadataResolverAdapter metadataResolver;
8467

8568
private final Supplier<Iterator<EntityDescriptor>> descriptors;
8669

8770
/**
88-
* Construct an {@link OpenSamlAssertingPartyMetadataRepository} using the provided
89-
* {@link MetadataResolver}.
71+
* Construct an {@link BaseOpenSamlAssertingPartyMetadataRepository} using the
72+
* provided {@link MetadataResolver}.
9073
*
9174
* <p>
9275
* The {@link MetadataResolver} should either be of type
9376
* {@link IterableMetadataSource} or it should have a {@link RoleMetadataIndex}
9477
* configured.
9578
* @param metadataResolver the {@link MetadataResolver} to use
9679
*/
97-
public OpenSamlAssertingPartyMetadataRepository(MetadataResolver metadataResolver) {
80+
BaseOpenSamlAssertingPartyMetadataRepository(MetadataResolverAdapter metadataResolver) {
9881
Assert.notNull(metadataResolver, "metadataResolver cannot be null");
99-
if (isRoleIndexed(metadataResolver)) {
82+
if (isRoleIndexed(metadataResolver.metadataResolver)) {
10083
this.descriptors = this::allIndexedEntities;
10184
}
102-
else if (metadataResolver instanceof IterableMetadataSource source) {
85+
else if (metadataResolver.metadataResolver instanceof IterableMetadataSource source) {
10386
this.descriptors = source::iterator;
10487
}
10588
else {
@@ -122,11 +105,11 @@ private static boolean isRoleIndexed(MetadataResolver resolver) {
122105
}
123106

124107
private Iterator<EntityDescriptor> allIndexedEntities() {
125-
CriteriaSet all = new CriteriaSet(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
108+
EntityRoleCriterion idps = new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
126109
try {
127-
return this.metadataResolver.resolve(all).iterator();
110+
return this.metadataResolver.resolve(idps).iterator();
128111
}
129-
catch (ResolverException ex) {
112+
catch (Exception ex) {
130113
throw new Saml2Exception(ex);
131114
}
132115
}
@@ -151,80 +134,30 @@ public AssertingPartyMetadata next() {
151134
@Nullable
152135
@Override
153136
public AssertingPartyMetadata findByEntityId(String entityId) {
154-
CriteriaSet byEntityId = new CriteriaSet(new EntityIdCriterion(entityId));
155-
EntityDescriptor descriptor = resolveSingle(byEntityId);
137+
EntityDescriptor descriptor = resolveSingle(new EntityIdCriterion(entityId));
156138
if (descriptor == null) {
157139
return null;
158140
}
159141
return OpenSamlAssertingPartyDetails.withEntityDescriptor(descriptor).build();
160142
}
161143

162-
private EntityDescriptor resolveSingle(CriteriaSet criteria) {
144+
private EntityDescriptor resolveSingle(EntityIdCriterion criterion) {
163145
try {
164-
return this.metadataResolver.resolveSingle(criteria);
146+
return this.metadataResolver.resolveSingle(criterion);
165147
}
166-
catch (ResolverException ex) {
148+
catch (Exception ex) {
167149
throw new Saml2Exception(ex);
168150
}
169151
}
170152

171153
/**
172-
* Use this trusted {@code metadataLocation} to retrieve refreshable, expiry-aware
173-
* SAML 2.0 Asserting Party (IDP) metadata.
174-
*
175-
* <p>
176-
* Valid locations can be classpath- or file-based or they can be HTTPS endpoints.
177-
* Some valid endpoints might include:
178-
*
179-
* <pre>
180-
* metadataLocation = "classpath:asserting-party-metadata.xml";
181-
* metadataLocation = "file:asserting-party-metadata.xml";
182-
* metadataLocation = "https://ap.example.org/metadata";
183-
* </pre>
184-
*
185-
* <p>
186-
* Resolution of location is attempted immediately. To defer, wrap in
187-
* {@link CachingRelyingPartyRegistrationRepository}.
188-
* @param metadataLocation the classpath- or file-based locations or HTTPS endpoints
189-
* of the asserting party metadata file
190-
* @return the {@link MetadataLocationRepositoryBuilder} for further configuration
191-
*/
192-
public static MetadataLocationRepositoryBuilder withTrustedMetadataLocation(String metadataLocation) {
193-
return new MetadataLocationRepositoryBuilder(metadataLocation, true);
194-
}
195-
196-
/**
197-
* Use this {@code metadataLocation} to retrieve refreshable, expiry-aware SAML 2.0
198-
* Asserting Party (IDP) metadata. Verification credentials are required.
199-
*
200-
* <p>
201-
* Valid locations can be classpath- or file-based or they can be remote endpoints.
202-
* Some valid endpoints might include:
203-
*
204-
* <pre>
205-
* metadataLocation = "classpath:asserting-party-metadata.xml";
206-
* metadataLocation = "file:asserting-party-metadata.xml";
207-
* metadataLocation = "https://ap.example.org/metadata";
208-
* </pre>
209-
*
210-
* <p>
211-
* Resolution of location is attempted immediately. To defer, wrap in
212-
* {@link CachingRelyingPartyRegistrationRepository}.
213-
* @param metadataLocation the classpath- or file-based locations or remote endpoints
214-
* of the asserting party metadata file
215-
* @return the {@link MetadataLocationRepositoryBuilder} for further configuration
216-
*/
217-
public static MetadataLocationRepositoryBuilder withMetadataLocation(String metadataLocation) {
218-
return new MetadataLocationRepositoryBuilder(metadataLocation, false);
219-
}
220-
221-
/**
222-
* A builder class for configuring {@link OpenSamlAssertingPartyMetadataRepository}
223-
* for a specific metadata location.
154+
* A builder class for configuring
155+
* {@link BaseOpenSamlAssertingPartyMetadataRepository} for a specific metadata
156+
* location.
224157
*
225158
* @author Josh Cummings
226159
*/
227-
public static final class MetadataLocationRepositoryBuilder {
160+
static final class MetadataLocationRepositoryBuilder {
228161

229162
private final String metadataLocation;
230163

@@ -234,56 +167,37 @@ public static final class MetadataLocationRepositoryBuilder {
234167

235168
private ResourceLoader resourceLoader = new DefaultResourceLoader();
236169

237-
private MetadataLocationRepositoryBuilder(String metadataLocation, boolean trusted) {
170+
MetadataLocationRepositoryBuilder(String metadataLocation, boolean trusted) {
238171
this.metadataLocation = metadataLocation;
239172
this.requireVerificationCredentials = !trusted;
240173
}
241174

242-
/**
243-
* Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s to use
244-
* for verifying metadata signatures.
245-
*
246-
* <p>
247-
* If no credentials are supplied, no signature verification is performed.
248-
* @param credentials a {@link Consumer} of the {@link Collection} of
249-
* {@link Saml2X509Credential}s
250-
* @return the {@link MetadataLocationRepositoryBuilder} for further configuration
251-
*/
252-
public MetadataLocationRepositoryBuilder verificationCredentials(Consumer<Collection<Credential>> credentials) {
175+
MetadataLocationRepositoryBuilder verificationCredentials(Consumer<Collection<Credential>> credentials) {
253176
credentials.accept(this.verificationCredentials);
254177
return this;
255178
}
256179

257-
/**
258-
* Use this {@link ResourceLoader} for resolving the {@code metadataLocation}
259-
* @param resourceLoader the {@link ResourceLoader} to use
260-
* @return the {@link MetadataLocationRepositoryBuilder} for further configuration
261-
*/
262-
public MetadataLocationRepositoryBuilder resourceLoader(ResourceLoader resourceLoader) {
180+
MetadataLocationRepositoryBuilder resourceLoader(ResourceLoader resourceLoader) {
263181
this.resourceLoader = resourceLoader;
264182
return this;
265183
}
266184

267-
/**
268-
* Build the {@link OpenSamlAssertingPartyMetadataRepository}
269-
* @return the {@link OpenSamlAssertingPartyMetadataRepository}
270-
*/
271-
public OpenSamlAssertingPartyMetadataRepository build() {
272-
ResourceBackedMetadataResolver metadataResolver = metadataResolver();
185+
MetadataResolver metadataResolver() {
186+
ResourceBackedMetadataResolver metadataResolver = resourceBackedMetadataResolver();
273187
if (!this.verificationCredentials.isEmpty()) {
274188
SignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine(
275189
new CollectionCredentialResolver(this.verificationCredentials),
276190
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
277191
SignatureValidationFilter filter = new SignatureValidationFilter(engine);
278192
filter.setRequireSignedRoot(true);
279193
metadataResolver.setMetadataFilter(filter);
280-
return new OpenSamlAssertingPartyMetadataRepository(initialize(metadataResolver));
194+
return initialize(metadataResolver);
281195
}
282196
Assert.isTrue(!this.requireVerificationCredentials, "Verification credentials are required");
283-
return new OpenSamlAssertingPartyMetadataRepository(initialize(metadataResolver));
197+
return initialize(metadataResolver);
284198
}
285199

286-
private ResourceBackedMetadataResolver metadataResolver() {
200+
private ResourceBackedMetadataResolver resourceBackedMetadataResolver() {
287201
Resource resource = this.resourceLoader.getResource(this.metadataLocation);
288202
try {
289203
return new ResourceBackedMetadataResolver(new SpringResource(resource));
@@ -301,7 +215,7 @@ private MetadataResolver initialize(ResourceBackedMetadataResolver metadataResol
301215
metadataResolver.initialize();
302216
return metadataResolver;
303217
}
304-
catch (ComponentInitializationException ex) {
218+
catch (Exception ex) {
305219
throw new Saml2Exception(ex);
306220
}
307221
}
@@ -380,4 +294,18 @@ public String getDescription() {
380294

381295
}
382296

297+
abstract static class MetadataResolverAdapter {
298+
299+
final MetadataResolver metadataResolver;
300+
301+
MetadataResolverAdapter(MetadataResolver metadataResolver) {
302+
this.metadataResolver = metadataResolver;
303+
}
304+
305+
abstract EntityDescriptor resolveSingle(EntityIdCriterion entityId) throws Exception;
306+
307+
abstract Iterable<EntityDescriptor> resolve(EntityRoleCriterion role) throws Exception;
308+
309+
}
310+
383311
}

0 commit comments

Comments
 (0)