Preserve OpenSamlAssertingPartyDetails Instance
Closes gh-12667
This commit is contained in:
parent
000b4bc495
commit
2db4430dcd
|
@ -1,5 +1,36 @@
|
||||||
[[servlet-saml2login-metadata]]
|
[[servlet-saml2login-metadata]]
|
||||||
= Producing `<saml2:SPSSODescriptor>` Metadata
|
= Saml 2.0 Metadata
|
||||||
|
|
||||||
|
Spring Security can <<parsing-asserting-party-metadata,parse asserting party metadata>> to produce an `AssertingPartyDetails` instance as well as <<publishing-relying-party-metadata,publish relying party metadata>> from a `RelyingPartyRegistration` instance.
|
||||||
|
|
||||||
|
[[parsing-asserting-party-metadata]]
|
||||||
|
== Parsing `<saml2:IDPSSODescriptor>` metadata
|
||||||
|
|
||||||
|
You can parse an asserting party's metadata xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[using `RelyingPartyRegistrations`].
|
||||||
|
|
||||||
|
When using the OpenSAML vendor support, the resulting `AssertingPartyDetails` will be of type `OpenSamlAssertingPartyDetails`.
|
||||||
|
This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Java
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails)
|
||||||
|
registration.getAssertingPartyDetails();
|
||||||
|
EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor();
|
||||||
|
----
|
||||||
|
|
||||||
|
.Kotlin
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
val details: OpenSamlAssertingPartyDetails =
|
||||||
|
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails;
|
||||||
|
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor();
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[publishing-relying-party-metadata]]
|
||||||
|
== Producing `<saml2:SPSSODescriptor>` Metadata
|
||||||
|
|
||||||
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
|
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2023 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
class OpenSamlMetadataRelyingPartyRegistrationConverter {
|
||||||
|
|
||||||
|
private final OpenSamlMetadataAssertingPartyDetailsConverter converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
|
||||||
|
|
||||||
|
Collection<RelyingPartyRegistration.Builder> convert(InputStream source) {
|
||||||
|
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
|
||||||
|
for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) {
|
||||||
|
builders.add(new RelyingPartyRegistration.Builder(builder));
|
||||||
|
}
|
||||||
|
return builders;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -89,8 +89,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
|
||||||
@Override
|
@Override
|
||||||
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
|
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
|
||||||
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
||||||
return RelyingPartyRegistration
|
return new RelyingPartyRegistration.Builder(this.converter.convert(inputMessage.getBody()).iterator().next());
|
||||||
.withAssertingPartyDetails(this.converter.convert(inputMessage.getBody()).iterator().next().build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,6 +30,7 @@ import java.util.function.Function;
|
||||||
|
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@ -970,6 +971,14 @@ public final class RelyingPartyRegistration {
|
||||||
|
|
||||||
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
|
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder(AssertingPartyDetails.Builder assertingPartyDetailsBuilder) {
|
||||||
|
this.assertingPartyDetailsBuilder = assertingPartyDetailsBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the asserting party's <a href=
|
* Set the asserting party's <a href=
|
||||||
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
|
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
|
||||||
|
@ -1032,7 +1041,7 @@ public final class RelyingPartyRegistration {
|
||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
|
||||||
private String registrationId;
|
private Converter<ProviderDetails, String> registrationId = ProviderDetails::getEntityId;
|
||||||
|
|
||||||
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
|
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
|
||||||
|
|
||||||
|
@ -1052,12 +1061,17 @@ public final class RelyingPartyRegistration {
|
||||||
|
|
||||||
private String nameIdFormat = null;
|
private String nameIdFormat = null;
|
||||||
|
|
||||||
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
|
private ProviderDetails.Builder providerDetails;
|
||||||
|
|
||||||
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new LinkedHashSet<>();
|
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new LinkedHashSet<>();
|
||||||
|
|
||||||
private Builder(String registrationId) {
|
private Builder(String registrationId) {
|
||||||
this.registrationId = registrationId;
|
this.registrationId = (party) -> registrationId;
|
||||||
|
this.providerDetails = new ProviderDetails.Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder(AssertingPartyDetails.Builder builder) {
|
||||||
|
this.providerDetails = new ProviderDetails.Builder(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1066,7 +1080,7 @@ public final class RelyingPartyRegistration {
|
||||||
* @return this object
|
* @return this object
|
||||||
*/
|
*/
|
||||||
public Builder registrationId(String id) {
|
public Builder registrationId(String id) {
|
||||||
this.registrationId = id;
|
this.registrationId = (party) -> id;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1363,11 +1377,12 @@ public final class RelyingPartyRegistration {
|
||||||
if (this.singleLogoutServiceResponseLocation == null) {
|
if (this.singleLogoutServiceResponseLocation == null) {
|
||||||
this.singleLogoutServiceResponseLocation = this.singleLogoutServiceLocation;
|
this.singleLogoutServiceResponseLocation = this.singleLogoutServiceLocation;
|
||||||
}
|
}
|
||||||
return new RelyingPartyRegistration(this.registrationId, this.entityId,
|
ProviderDetails party = this.providerDetails.build();
|
||||||
this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
|
String registrationId = this.registrationId.convert(party);
|
||||||
this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,
|
return new RelyingPartyRegistration(registrationId, this.entityId, this.assertionConsumerServiceLocation,
|
||||||
this.singleLogoutServiceBinding, this.providerDetails.build(), this.nameIdFormat, this.credentials,
|
this.assertionConsumerServiceBinding, this.singleLogoutServiceLocation,
|
||||||
this.decryptionX509Credentials, this.signingX509Credentials);
|
this.singleLogoutServiceResponseLocation, this.singleLogoutServiceBinding, party, this.nameIdFormat,
|
||||||
|
this.credentials, this.decryptionX509Credentials, this.signingX509Credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,13 +18,11 @@ package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.security.saml2.Saml2Exception;
|
import org.springframework.security.saml2.Saml2Exception;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class for constructing instances of {@link RelyingPartyRegistration}
|
* A utility class for constructing instances of {@link RelyingPartyRegistration}
|
||||||
|
@ -36,7 +34,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
|
||||||
*/
|
*/
|
||||||
public final class RelyingPartyRegistrations {
|
public final class RelyingPartyRegistrations {
|
||||||
|
|
||||||
private static final OpenSamlMetadataAssertingPartyDetailsConverter assertingPartyMetadataConverter = new OpenSamlMetadataAssertingPartyDetailsConverter();
|
private static final OpenSamlMetadataRelyingPartyRegistrationConverter relyingPartyRegistrationConverter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
|
||||||
|
|
||||||
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
|
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
|
|
||||||
|
@ -215,11 +213,7 @@ public final class RelyingPartyRegistrations {
|
||||||
* @since 5.7
|
* @since 5.7
|
||||||
*/
|
*/
|
||||||
public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) {
|
public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) {
|
||||||
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
|
return relyingPartyRegistrationConverter.convert(source);
|
||||||
for (AssertingPartyDetails.Builder builder : assertingPartyMetadataConverter.convert(source)) {
|
|
||||||
builders.add(RelyingPartyRegistration.withAssertingPartyDetails(builder.build()));
|
|
||||||
}
|
|
||||||
return builders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2023 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
|
||||||
|
|
||||||
|
private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
|
||||||
|
|
||||||
|
private String metadata;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() throws Exception {
|
||||||
|
ClassPathResource resource = new ClassPathResource("test-metadata.xml");
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
|
||||||
|
this.metadata = reader.lines().collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-12667
|
||||||
|
@Test
|
||||||
|
public void convertWhenDefaultsThenAssertingPartyInstanceOfOpenSaml() throws Exception {
|
||||||
|
try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
this.converter.convert(source)
|
||||||
|
.forEach((registration) -> assertThat(registration.build().getAssertingPartyDetails())
|
||||||
|
.isInstanceOf(OpenSamlAssertingPartyDetails.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue