Use AssertingPartyMetadata

Issue gh-15394
This commit is contained in:
Josh Cummings 2024-07-19 18:24:25 -06:00
parent dfa67fd8a1
commit 9d8888c5f0
29 changed files with 320 additions and 79 deletions

View File

@ -39,6 +39,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.converter.RsaKeyConverters;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
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.RelyingPartyRegistrations;
@ -153,7 +154,7 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
}
private static void addVerificationCredentials(Map<String, Object> assertingParty,
RelyingPartyRegistration.AssertingPartyDetails.Builder builder) {
AssertingPartyMetadata.Builder<?> builder) {
List<String> verificationCertificateLocations = (List<String>) assertingParty.get(ELT_VERIFICATION_CREDENTIAL);
List<Saml2X509Credential> verificationCredentials = new ArrayList<>();
for (String certificateLocation : verificationCertificateLocations) {
@ -163,7 +164,7 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
}
private static void addEncryptionCredentials(Map<String, Object> assertingParty,
RelyingPartyRegistration.AssertingPartyDetails.Builder builder) {
AssertingPartyMetadata.Builder<?> builder) {
List<String> encryptionCertificateLocations = (List<String>) assertingParty.get(ELT_ENCRYPTION_CREDENTIAL);
List<Saml2X509Credential> encryptionCredentials = new ArrayList<>();
for (String certificateLocation : encryptionCertificateLocations) {
@ -220,8 +221,8 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
}
else {
builder = RelyingPartyRegistration.withRegistrationId(registrationId)
.assertingPartyDetails((apBuilder) -> buildAssertingParty(relyingPartyRegistrationElt, assertingParties,
apBuilder, parserContext));
.assertingPartyMetadata((apBuilder) -> buildAssertingParty(relyingPartyRegistrationElt,
assertingParties, apBuilder, parserContext));
}
addRemainingProperties(relyingPartyRegistrationElt, builder);
return builder;
@ -260,7 +261,7 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
}
private static void buildAssertingParty(Element relyingPartyElt, Map<String, Map<String, Object>> assertingParties,
RelyingPartyRegistration.AssertingPartyDetails.Builder builder, ParserContext parserContext) {
AssertingPartyMetadata.Builder<?> builder, ParserContext parserContext) {
String assertingPartyId = relyingPartyElt.getAttribute(ATT_ASSERTING_PARTY_ID);
if (!assertingParties.containsKey(assertingPartyId)) {
Object source = parserContext.extractSource(relyingPartyElt);
@ -293,7 +294,7 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
}
private static void addSigningAlgorithms(Map<String, Object> assertingParty,
RelyingPartyRegistration.AssertingPartyDetails.Builder builder) {
AssertingPartyMetadata.Builder<?> builder) {
String signingAlgorithmsAttr = getAsString(assertingParty, ATT_SIGNING_ALGORITHMS);
if (StringUtils.hasText(signingAlgorithmsAttr)) {
List<String> signingAlgorithms = Arrays.asList(signingAlgorithmsAttr.split(","));

View File

@ -114,7 +114,7 @@ Java::
----
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails(party -> party
.assertingPartyMetadata(party -> party
// ...
.wantAuthnRequestsSigned(false)
)
@ -128,7 +128,7 @@ Kotlin::
var relyingPartyRegistration: RelyingPartyRegistration =
RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
// ...
.wantAuthnRequestsSigned(false)
}
@ -154,7 +154,7 @@ Java::
String metadataLocation = "classpath:asserting-party-metadata.xml";
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
// ...
.assertingPartyDetails((party) -> party
.assertingPartyMetadata((party) -> party
// ...
.signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
)
@ -169,7 +169,7 @@ var metadataLocation = "classpath:asserting-party-metadata.xml"
var relyingPartyRegistration: RelyingPartyRegistration =
RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
// ...
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
// ...
.signingAlgorithms { sign: MutableList<String?> ->
sign.add(
@ -197,7 +197,7 @@ Java::
----
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails(party -> party
.assertingPartyMetadata(party -> party
// ...
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
)
@ -211,7 +211,7 @@ Kotlin::
var relyingPartyRegistration: RelyingPartyRegistration? =
RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
// ...
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
}

View File

@ -484,7 +484,7 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exc
Saml2X509Credential credential = Saml2X509Credential.verification(certificate);
RelyingPartyRegistration registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyDetails(party -> party
.assertingPartyMetadata(party -> party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
.wantAuthnRequestsSigned(false)
@ -508,7 +508,7 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository {
val credential: Saml2X509Credential = Saml2X509Credential.verification(certificate)
val registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyDetails { party: AssertingPartyDetails.Builder ->
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder ->
party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
@ -699,7 +699,7 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
.entityId("{baseUrl}/{registrationId}")
.decryptionX509Credentials(c -> c.add(relyingPartyDecryptingCredential()))
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyDetails(party -> party
.assertingPartyMetadata(party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials(c -> c.add(assertingPartyVerifyingCredential()))
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
@ -718,7 +718,7 @@ val relyingPartyRegistration =
c.add(relyingPartyDecryptingCredential())
}
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyDetails { party -> party
.assertingPartyMetadata { party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials { c -> c.add(assertingPartyVerifyingCredential()) }
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
@ -730,7 +730,7 @@ val relyingPartyRegistration =
[TIP]
====
The top-level metadata methods are details about the relying party.
The methods inside `assertingPartyDetails` are details about the asserting party.
The methods inside `AssertingPartyMetadata` are details about the asserting party.
====
[NOTE]

View File

@ -339,7 +339,7 @@ It's common to need to set other values in the `<saml2:LogoutRequest>` than the
By default, Spring Security will issue a `<saml2:LogoutRequest>` and supply:
* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation`
* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyMetadata#getSingleLogoutServiceLocation`
* The `ID` attribute - a GUID
* The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
* The `<NameID>` element - from `Authentication#getName`
@ -424,7 +424,7 @@ It's common to need to set other values in the `<saml2:LogoutResponse>` than the
By default, Spring Security will issue a `<saml2:LogoutResponse>` and supply:
* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation`
* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyMetadata#getSingleLogoutServiceResponseLocation`
* The `ID` attribute - a GUID
* The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
* The `<Status>` element - `SUCCESS`

View File

@ -1,14 +1,14 @@
[[servlet-saml2login-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.
Spring Security can <<parsing-asserting-party-metadata,parse asserting party metadata>> to produce an `AssertingPartyMetadata` 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`.
When using the OpenSAML vendor support, the resulting `AssertingPartyMetadata` will be of type `OpenSamlAssertingPartyDetails`.
This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following:
[tabs]
@ -18,7 +18,7 @@ Java::
[source,java,role="primary"]
----
OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails)
registration.getAssertingPartyDetails();
registration.getAssertingPartyMetadata();
EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor();
----
@ -27,7 +27,7 @@ Kotlin::
[source,kotlin,role="secondary"]
----
val details: OpenSamlAssertingPartyDetails =
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails
registration.getAssertingPartyMetadata() as OpenSamlAssertingPartyDetails
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor()
----
======
@ -76,8 +76,7 @@ public class RefreshableRelyingPartyRegistrationRepository
}
private RelyingPartyRegistration applyRelyingParty(AssertingPartyMetadata metadata) {
AssertingPartyDetails details = (AssertingPartyDetails) metadata;
return RelyingPartyRegistration.withAssertingPartyDetails(details)
return RelyingPartyRegistration.withAssertingPartyMetadata(metadata)
// apply any relying party configuration
.build();
}
@ -110,8 +109,8 @@ class RefreshableRelyingPartyRegistrationRepository : IterableRelyingPartyRegist
}
private fun applyRelyingParty(metadata: AssertingPartyMetadata): RelyingPartyRegistration {
val details: AssertingPartyDetails = metadata as AssertingPartyDetails
return RelyingPartyRegistration.withAssertingPartyDetails(details)
val details: AssertingPartyMetadata = metadata as AssertingPartyMetadata
return RelyingPartyRegistration.withAssertingPartyMetadata(details)
// apply any relying party configuration
.build()
}

View File

@ -400,7 +400,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, message));
}
String assertingPartyEntityId = token.getRelyingPartyRegistration()
.getAssertingPartyDetails()
.getAssertingPartyMetadata()
.getEntityId();
if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) {
String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID());
@ -775,7 +775,7 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
RelyingPartyRegistration relyingPartyRegistration = token.getRelyingPartyRegistration();
String audience = relyingPartyRegistration.getEntityId();
String recipient = relyingPartyRegistration.getAssertionConsumerServiceLocation();
String assertingPartyEntityId = relyingPartyRegistration.getAssertingPartyDetails().getEntityId();
String assertingPartyEntityId = relyingPartyRegistration.getAssertingPartyMetadata().getEntityId();
Map<String, Object> params = new HashMap<>();
Assertion assertion = assertionToken.getAssertion();
if (assertionContainsInResponseTo(assertion)) {

View File

@ -96,7 +96,7 @@ final class OpenSamlSigningUtils {
private static SignatureSigningParameters resolveSigningParameters(
RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<String> algorithms = relyingPartyRegistration.getAssertingPartyMetadata().getSigningAlgorithms();
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();

View File

@ -73,11 +73,12 @@ final class OpenSamlVerificationUtils {
static SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) {
Set<Credential> credentials = new HashSet<>();
Collection<Saml2X509Credential> keys = registration.getAssertingPartyDetails().getVerificationX509Credentials();
Collection<Saml2X509Credential> keys = registration.getAssertingPartyMetadata()
.getVerificationX509Credentials();
for (Saml2X509Credential key : keys) {
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
cred.setUsageType(UsageType.SIGNING);
cred.setEntityId(registration.getAssertingPartyDetails().getEntityId());
cred.setEntityId(registration.getAssertingPartyMetadata().getEntityId());
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);

View File

@ -50,7 +50,7 @@ public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationR
* @since 5.7
*/
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
String location = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
String location = registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation();
return new Builder(registration).authenticationRequestUri(location);
}

View File

@ -73,7 +73,7 @@ public final class Saml2RedirectAuthenticationRequest extends AbstractSaml2Authe
* @since 5.7
*/
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
String location = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
String location = registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation();
return new Builder(registration).authenticationRequestUri(location);
}

View File

@ -134,7 +134,7 @@ public final class OpenSamlLogoutRequestValidator implements Saml2LogoutRequestV
return;
}
String issuer = request.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyDetails().getEntityId())) {
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}

View File

@ -132,7 +132,7 @@ public class OpenSamlLogoutResponseValidator implements Saml2LogoutResponseValid
return;
}
String issuer = response.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyDetails().getEntityId())) {
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}

View File

@ -164,12 +164,12 @@ final class OpenSamlVerificationUtils {
private SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) {
Set<Credential> credentials = new HashSet<>();
Collection<Saml2X509Credential> keys = registration.getAssertingPartyDetails()
Collection<Saml2X509Credential> keys = registration.getAssertingPartyMetadata()
.getVerificationX509Credentials();
for (Saml2X509Credential key : keys) {
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
cred.setUsageType(UsageType.SIGNING);
cred.setEntityId(registration.getAssertingPartyDetails().getEntityId());
cred.setEntityId(registration.getAssertingPartyMetadata().getEntityId());
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);

View File

@ -190,8 +190,8 @@ public final class Saml2LogoutRequest implements Serializable {
private Builder(RelyingPartyRegistration registration) {
this.registration = registration;
this.location = registration.getAssertingPartyDetails().getSingleLogoutServiceLocation();
this.binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding();
this.location = registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation();
this.binding = registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding();
}
/**

View File

@ -156,8 +156,8 @@ public final class Saml2LogoutResponse {
private Function<Map<String, String>, String> encoder = DEFAULT_ENCODER;
private Builder(RelyingPartyRegistration registration) {
this.location = registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation();
this.binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding();
this.location = registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation();
this.binding = registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding();
}
/**

View File

@ -80,7 +80,7 @@ final class OpenSamlSigningUtils {
}
static <O extends SignableXMLObject> O sign(O object, RelyingPartyRegistration relyingPartyRegistration) {
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<String> algorithms = relyingPartyRegistration.getAssertingPartyMetadata().getSigningAlgorithms();
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
return sign(object, algorithms, credentials);
}
@ -103,7 +103,7 @@ final class OpenSamlSigningUtils {
private static SignatureSigningParameters resolveSigningParameters(
RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<String> algorithms = relyingPartyRegistration.getAssertingPartyMetadata().getSigningAlgorithms();
return resolveSigningParameters(algorithms, credentials);
}

View File

@ -69,7 +69,7 @@ public class InMemoryRelyingPartyRegistrationRepository implements IterableRelyi
Collection<RelyingPartyRegistration> rps) {
MultiValueMap<String, RelyingPartyRegistration> result = new LinkedMultiValueMap<>();
for (RelyingPartyRegistration rp : rps) {
result.add(rp.getAssertingPartyDetails().getEntityId(), rp);
result.add(rp.getAssertingPartyMetadata().getEntityId(), rp);
}
return Collections.unmodifiableMap(result);
}

View File

@ -36,7 +36,7 @@ import org.springframework.security.saml2.core.Saml2X509Credential;
* EntityDescriptor descriptor = openSamlRegistration.getAssertingPartyDetails.getEntityDescriptor();
* }
* </pre> do instead: <pre>
* if (registration.getAssertingPartyDetails() instanceof openSamlAssertingPartyDetails) {
* if (registration.getAssertingPartyMetadata() instanceof openSamlAssertingPartyDetails) {
* EntityDescriptor descriptor = openSamlAssertingPartyDetails.getEntityDescriptor();
* }
* </pre>
@ -170,6 +170,11 @@ public final class OpenSamlRelyingPartyRegistration extends RelyingPartyRegistra
return (Builder) super.assertingPartyDetails(assertingPartyDetails);
}
@Override
public Builder assertingPartyMetadata(Consumer<AssertingPartyMetadata.Builder<?>> assertingPartyMetadata) {
return (Builder) super.assertingPartyMetadata(assertingPartyMetadata);
}
/**
* Build an {@link OpenSamlRelyingPartyRegistration}
* {@link org.springframework.security.saml2.provider.service.registration.OpenSamlRelyingPartyRegistration}

View File

@ -88,7 +88,7 @@ public class RelyingPartyRegistration {
private final boolean authnRequestsSigned;
private final AssertingPartyDetails assertingPartyDetails;
private final AssertingPartyMetadata assertingPartyMetadata;
private final Collection<Saml2X509Credential> decryptionX509Credentials;
@ -127,7 +127,45 @@ public class RelyingPartyRegistration {
this.singleLogoutServiceBindings = Collections.unmodifiableList(new LinkedList<>(singleLogoutServiceBindings));
this.nameIdFormat = nameIdFormat;
this.authnRequestsSigned = authnRequestsSigned;
this.assertingPartyDetails = assertingPartyDetails;
this.assertingPartyMetadata = assertingPartyDetails;
this.decryptionX509Credentials = Collections.unmodifiableList(new LinkedList<>(decryptionX509Credentials));
this.signingX509Credentials = Collections.unmodifiableList(new LinkedList<>(signingX509Credentials));
}
private RelyingPartyRegistration(String registrationId, String entityId, String assertionConsumerServiceLocation,
Saml2MessageBinding assertionConsumerServiceBinding, String singleLogoutServiceLocation,
String singleLogoutServiceResponseLocation, Collection<Saml2MessageBinding> singleLogoutServiceBindings,
AssertingPartyMetadata assertingPartyMetadata, String nameIdFormat, boolean authnRequestsSigned,
Collection<Saml2X509Credential> decryptionX509Credentials,
Collection<Saml2X509Credential> signingX509Credentials) {
Assert.hasText(registrationId, "registrationId cannot be empty");
Assert.hasText(entityId, "entityId cannot be empty");
Assert.hasText(assertionConsumerServiceLocation, "assertionConsumerServiceLocation cannot be empty");
Assert.notNull(assertionConsumerServiceBinding, "assertionConsumerServiceBinding cannot be null");
Assert.isTrue(singleLogoutServiceLocation == null || !CollectionUtils.isEmpty(singleLogoutServiceBindings),
"singleLogoutServiceBindings cannot be null or empty when singleLogoutServiceLocation is set");
Assert.notNull(assertingPartyMetadata, "assertingPartyDetails cannot be null");
Assert.notNull(decryptionX509Credentials, "decryptionX509Credentials cannot be null");
for (Saml2X509Credential c : decryptionX509Credentials) {
Assert.notNull(c, "decryptionX509Credentials cannot contain null elements");
Assert.isTrue(c.isDecryptionCredential(),
"All decryptionX509Credentials must have a usage of DECRYPTION set");
}
Assert.notNull(signingX509Credentials, "signingX509Credentials cannot be null");
for (Saml2X509Credential c : signingX509Credentials) {
Assert.notNull(c, "signingX509Credentials cannot contain null elements");
Assert.isTrue(c.isSigningCredential(), "All signingX509Credentials must have a usage of SIGNING set");
}
this.registrationId = registrationId;
this.entityId = entityId;
this.assertionConsumerServiceLocation = assertionConsumerServiceLocation;
this.assertionConsumerServiceBinding = assertionConsumerServiceBinding;
this.singleLogoutServiceLocation = singleLogoutServiceLocation;
this.singleLogoutServiceResponseLocation = singleLogoutServiceResponseLocation;
this.singleLogoutServiceBindings = Collections.unmodifiableList(new LinkedList<>(singleLogoutServiceBindings));
this.nameIdFormat = nameIdFormat;
this.authnRequestsSigned = authnRequestsSigned;
this.assertingPartyMetadata = assertingPartyMetadata;
this.decryptionX509Credentials = Collections.unmodifiableList(new LinkedList<>(decryptionX509Credentials));
this.signingX509Credentials = Collections.unmodifiableList(new LinkedList<>(signingX509Credentials));
}
@ -139,7 +177,7 @@ public class RelyingPartyRegistration {
* @since 6.1
*/
public Builder mutate() {
return new Builder(this.registrationId, this.assertingPartyDetails.mutate()).entityId(this.entityId)
return new Builder(this.registrationId, this.assertingPartyMetadata.mutate()).entityId(this.entityId)
.signingX509Credentials((c) -> c.addAll(this.signingX509Credentials))
.decryptionX509Credentials((c) -> c.addAll(this.decryptionX509Credentials))
.assertionConsumerServiceLocation(this.assertionConsumerServiceLocation)
@ -317,9 +355,22 @@ public class RelyingPartyRegistration {
* Get the configuration details for the Asserting Party
* @return the {@link AssertingPartyDetails}
* @since 5.4
* @deprecated Use {@link #getAssertingPartyMetadata()} instead
*/
@Deprecated
public AssertingPartyDetails getAssertingPartyDetails() {
return this.assertingPartyDetails;
Assert.isInstanceOf(AssertingPartyDetails.class, this.assertingPartyMetadata,
"This class was initialized with an AssertingPartyMetadata, please call #getAssertingPartyMetadata instead");
return (AssertingPartyDetails) this.assertingPartyMetadata;
}
/**
* Get the metadata for the Asserting Party
* @return the {@link AssertingPartyDetails}
* @since 6.4
*/
public AssertingPartyMetadata getAssertingPartyMetadata() {
return this.assertingPartyMetadata;
}
/**
@ -333,6 +384,12 @@ public class RelyingPartyRegistration {
return new Builder(registrationId, new AssertingPartyDetails.Builder());
}
/**
* @param assertingPartyDetails the asserting party metadata
* @return {@code Builder} to create a {@code RelyingPartyRegistration} object
* @deprecated Use {@link #withAssertingPartyMetadata} instead
*/
@Deprecated(forRemoval = true, since = "6.4")
public static Builder withAssertingPartyDetails(AssertingPartyDetails assertingPartyDetails) {
Assert.notNull(assertingPartyDetails, "assertingPartyDetails cannot be null");
return new Builder(assertingPartyDetails.getEntityId(), assertingPartyDetails.mutate());
@ -353,8 +410,8 @@ public class RelyingPartyRegistration {
* @since 6.4
*/
public static Builder withAssertingPartyMetadata(AssertingPartyMetadata metadata) {
Assert.isInstanceOf(AssertingPartyDetails.class, metadata, "metadata must be of type AssertingPartyDetails");
return withAssertingPartyDetails((AssertingPartyDetails) metadata);
Assert.notNull(metadata, "assertingPartyMetadata cannot be null");
return new Builder(metadata.getEntityId(), metadata.mutate());
}
/**
@ -819,11 +876,11 @@ public class RelyingPartyRegistration {
private boolean authnRequestsSigned = false;
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder;
private AssertingPartyMetadata.Builder<?> assertingPartyMetadataBuilder;
protected Builder(String registrationId, AssertingPartyDetails.Builder assertingPartyDetailsBuilder) {
protected Builder(String registrationId, AssertingPartyMetadata.Builder<?> assertingPartyMetadataBuilder) {
this.registrationId = registrationId;
this.assertingPartyDetailsBuilder = assertingPartyDetailsBuilder;
this.assertingPartyMetadataBuilder = assertingPartyMetadataBuilder;
}
/**
@ -1028,9 +1085,24 @@ public class RelyingPartyRegistration {
* @param assertingPartyDetails The {@link Consumer} to apply
* @return the {@link Builder} for further configuration
* @since 5.4
* @deprecated Use {@link #assertingPartyMetadata} instead
*/
@Deprecated(forRemoval = true, since = "6.4")
public Builder assertingPartyDetails(Consumer<AssertingPartyDetails.Builder> assertingPartyDetails) {
assertingPartyDetails.accept(this.assertingPartyDetailsBuilder);
Assert.isInstanceOf(AssertingPartyDetails.Builder.class, this.assertingPartyMetadataBuilder,
"This class was constructed with an AssertingPartyMetadata instance, as such, please use #assertingPartyMetadata");
assertingPartyDetails.accept((AssertingPartyDetails.Builder) this.assertingPartyMetadataBuilder);
return this;
}
/**
* Apply this {@link Consumer} to further configure the Asserting Party metadata
* @param assertingPartyMetadata The {@link Consumer} to apply
* @return the {@link Builder} for further configuration
* @since 6.4
*/
public Builder assertingPartyMetadata(Consumer<AssertingPartyMetadata.Builder<?>> assertingPartyMetadata) {
assertingPartyMetadata.accept(this.assertingPartyMetadataBuilder);
return this;
}
@ -1048,7 +1120,7 @@ public class RelyingPartyRegistration {
this.singleLogoutServiceBindings.add(Saml2MessageBinding.POST);
}
AssertingPartyDetails party = this.assertingPartyDetailsBuilder.build();
AssertingPartyMetadata party = this.assertingPartyMetadataBuilder.build();
return new RelyingPartyRegistration(this.registrationId, this.entityId,
this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,

View File

@ -68,7 +68,7 @@ public final class RelyingPartyRegistrationPlaceholderResolvers {
*/
public static UriResolver uriResolver(HttpServletRequest request, RelyingPartyRegistration registration) {
String relyingPartyEntityId = registration.getEntityId();
String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
String assertingPartyEntityId = registration.getAssertingPartyMetadata().getEntityId();
String registrationId = registration.getRegistrationId();
Map<String, String> uriVariables = uriVariables(request);
uriVariables.put("relyingPartyEntityId", StringUtils.hasText(relyingPartyEntityId) ? relyingPartyEntityId : "");

View File

@ -146,7 +146,7 @@ class OpenSamlAuthenticationRequestResolver {
Issuer iss = this.issuerBuilder.buildObject();
iss.setValue(entityId);
authnRequest.setIssuer(iss);
authnRequest.setDestination(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
authnRequest.setDestination(registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation());
authnRequest.setAssertionConsumerServiceURL(assertionConsumerServiceLocation);
if (registration.getNameIdFormat() != null) {
NameIDPolicy nameIdPolicy = this.nameIdPolicyBuilder.buildObject();
@ -158,9 +158,9 @@ class OpenSamlAuthenticationRequestResolver {
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
}
String relayState = this.relayStateResolver.convert(request);
Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleSignOnServiceBinding();
Saml2MessageBinding binding = registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding();
if (binding == Saml2MessageBinding.POST) {
if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()
if (registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()
|| registration.isAuthnRequestsSigned()) {
OpenSamlSigningUtils.sign(authnRequest, registration);
}
@ -180,7 +180,7 @@ class OpenSamlAuthenticationRequestResolver {
.samlRequest(deflatedAndEncoded)
.relayState(relayState)
.id(authnRequest.getID());
if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()
if (registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()
|| registration.isAuthnRequestsSigned()) {
OpenSamlSigningUtils.QueryParametersPartial parametersPartial = OpenSamlSigningUtils.sign(registration)
.param(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded);

View File

@ -95,7 +95,7 @@ final class OpenSamlSigningUtils {
private static SignatureSigningParameters resolveSigningParameters(
RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<String> algorithms = relyingPartyRegistration.getAssertingPartyMetadata().getSigningAlgorithms();
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();

View File

@ -155,12 +155,12 @@ final class OpenSamlVerificationUtils {
private SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) {
Set<Credential> credentials = new HashSet<>();
Collection<Saml2X509Credential> keys = registration.getAssertingPartyDetails()
Collection<Saml2X509Credential> keys = registration.getAssertingPartyMetadata()
.getVerificationX509Credentials();
for (Saml2X509Credential key : keys) {
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
cred.setUsageType(UsageType.SIGNING);
cred.setEntityId(registration.getAssertingPartyDetails().getEntityId());
cred.setEntityId(registration.getAssertingPartyMetadata().getEntityId());
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);

View File

@ -126,13 +126,13 @@ final class OpenSamlLogoutRequestResolver {
if (registration == null) {
return null;
}
if (registration.getAssertingPartyDetails().getSingleLogoutServiceLocation() == null) {
if (registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation() == null) {
return null;
}
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
String entityId = uriResolver.resolve(registration.getEntityId());
LogoutRequest logoutRequest = this.logoutRequestBuilder.buildObject();
logoutRequest.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation());
logoutRequest.setDestination(registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation());
Issuer issuer = this.issuerBuilder.buildObject();
issuer.setValue(entityId);
logoutRequest.setIssuer(issuer);
@ -154,7 +154,7 @@ final class OpenSamlLogoutRequestResolver {
String relayState = this.relayStateResolver.convert(request);
Saml2LogoutRequest.Builder result = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id(logoutRequest.getID());
if (registration.getAssertingPartyDetails().getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) {
if (registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) {
String xml = serialize(OpenSamlSigningUtils.sign(logoutRequest, registration));
String samlRequest = Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8));
return result.samlRequest(samlRequest).relayState(relayState).build();

View File

@ -143,13 +143,14 @@ final class OpenSamlLogoutResponseResolver {
if (registration == null) {
return null;
}
if (registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation() == null) {
if (registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation() == null) {
return null;
}
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
String entityId = uriResolver.resolve(registration.getEntityId());
LogoutResponse logoutResponse = this.logoutResponseBuilder.buildObject();
logoutResponse.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation());
logoutResponse
.setDestination(registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation());
Issuer issuer = this.issuerBuilder.buildObject();
issuer.setValue(entityId);
logoutResponse.setIssuer(issuer);
@ -164,7 +165,7 @@ final class OpenSamlLogoutResponseResolver {
}
logoutResponseConsumer.accept(registration, logoutResponse);
Saml2LogoutResponse.Builder result = Saml2LogoutResponse.withRelyingPartyRegistration(registration);
if (registration.getAssertingPartyDetails().getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) {
if (registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) {
String xml = serialize(OpenSamlSigningUtils.sign(logoutResponse, registration));
String samlResponse = Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8));
result.samlResponse(samlResponse);

View File

@ -96,7 +96,7 @@ final class OpenSamlSigningUtils {
private static SignatureSigningParameters resolveSigningParameters(
RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<String> algorithms = relyingPartyRegistration.getAssertingPartyMetadata().getSigningAlgorithms();
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();

View File

@ -28,7 +28,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
*
* The returned logout request is suitable for sending to the asserting party based on,
* for example, the location and binding specified in
* {@link RelyingPartyRegistration#getAssertingPartyDetails()}.
* {@link RelyingPartyRegistration#getAssertingPartyMetadata()}.
*
* @author Josh Cummings
* @since 5.6

View File

@ -28,7 +28,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
*
* The returned logout response is suitable for sending to the asserting party based on,
* for example, the location and binding specified in
* {@link RelyingPartyRegistration#getAssertingPartyDetails()}.
* {@link RelyingPartyRegistration#getAssertingPartyMetadata()}.
*
* @author Josh Cummings
* @since 5.6

View File

@ -16,13 +16,19 @@
package org.springframework.security.saml2.provider.service.registration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class RelyingPartyRegistrationTests {
@ -167,16 +173,16 @@ public class RelyingPartyRegistrationTests {
}
@Test
void withAssertingPartyMetadataWhenDetailsThenBuilderCopies() {
void withAssertingPartyMetadataWhenMetadataThenBuilderCopies() {
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.nameIdFormat("format")
.assertingPartyDetails((a) -> a.singleSignOnServiceBinding(Saml2MessageBinding.POST))
.assertingPartyDetails((a) -> a.wantAuthnRequestsSigned(false))
.assertingPartyDetails((a) -> a.signingAlgorithms((algs) -> algs.add("alg")))
.assertingPartyMetadata((a) -> a.singleSignOnServiceBinding(Saml2MessageBinding.POST))
.assertingPartyMetadata((a) -> a.wantAuthnRequestsSigned(false))
.assertingPartyMetadata((a) -> a.signingAlgorithms((algs) -> algs.add("alg")))
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
.build();
RelyingPartyRegistration copied = RelyingPartyRegistration
.withAssertingPartyMetadata(registration.getAssertingPartyDetails())
.withAssertingPartyMetadata(registration.getAssertingPartyMetadata())
.registrationId(registration.getRegistrationId())
.entityId(registration.getEntityId())
.signingX509Credentials((c) -> c.addAll(registration.getSigningX509Credentials()))
@ -192,4 +198,160 @@ public class RelyingPartyRegistrationTests {
compareRegistrations(registration, copied);
}
@Test
void withAssertingPartyMetadataWhenMetadataThenDisallowsDetails() {
AssertingPartyMetadata metadata = new CustomAssertingPartyMetadata();
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> RelyingPartyRegistration.withAssertingPartyMetadata(metadata)
.assertingPartyDetails((a) -> a.entityId("entity-id"))
.build());
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(
() -> RelyingPartyRegistration.withAssertingPartyMetadata(metadata).build().getAssertingPartyDetails());
}
@Test
void withAssertingPartyMetadataWhenDetailsThenBuilderCopies() {
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.nameIdFormat("format")
.assertingPartyMetadata((a) -> a.singleSignOnServiceBinding(Saml2MessageBinding.POST))
.assertingPartyMetadata((a) -> a.wantAuthnRequestsSigned(false))
.assertingPartyMetadata((a) -> a.signingAlgorithms((algs) -> algs.add("alg")))
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
.build();
AssertingPartyDetails details = registration.getAssertingPartyDetails();
RelyingPartyRegistration copied = RelyingPartyRegistration.withAssertingPartyDetails(details)
.assertingPartyDetails((a) -> a.entityId(details.getEntityId()))
.registrationId(registration.getRegistrationId())
.entityId(registration.getEntityId())
.signingX509Credentials((c) -> c.addAll(registration.getSigningX509Credentials()))
.decryptionX509Credentials((c) -> c.addAll(registration.getDecryptionX509Credentials()))
.assertionConsumerServiceLocation(registration.getAssertionConsumerServiceLocation())
.assertionConsumerServiceBinding(registration.getAssertionConsumerServiceBinding())
.singleLogoutServiceLocation(registration.getSingleLogoutServiceLocation())
.singleLogoutServiceResponseLocation(registration.getSingleLogoutServiceResponseLocation())
.singleLogoutServiceBindings((c) -> c.addAll(registration.getSingleLogoutServiceBindings()))
.nameIdFormat(registration.getNameIdFormat())
.authnRequestsSigned(registration.isAuthnRequestsSigned())
.build();
compareRegistrations(registration, copied);
}
private static class CustomAssertingPartyMetadata implements AssertingPartyMetadata {
@Override
public String getEntityId() {
return "";
}
@Override
public boolean getWantAuthnRequestsSigned() {
return false;
}
@Override
public List<String> getSigningAlgorithms() {
return List.of();
}
@Override
public Collection<Saml2X509Credential> getVerificationX509Credentials() {
return List.of();
}
@Override
public Collection<Saml2X509Credential> getEncryptionX509Credentials() {
return List.of();
}
@Override
public String getSingleSignOnServiceLocation() {
return "";
}
@Override
public Saml2MessageBinding getSingleSignOnServiceBinding() {
return null;
}
@Override
public String getSingleLogoutServiceLocation() {
return "";
}
@Override
public String getSingleLogoutServiceResponseLocation() {
return "";
}
@Override
public Saml2MessageBinding getSingleLogoutServiceBinding() {
return null;
}
@Override
public Builder mutate() {
return new Builder();
}
private static class Builder implements AssertingPartyMetadata.Builder<Builder> {
@Override
public Builder entityId(String entityId) {
return this;
}
@Override
public Builder wantAuthnRequestsSigned(boolean wantAuthnRequestsSigned) {
return this;
}
@Override
public Builder signingAlgorithms(Consumer<List<String>> signingMethodAlgorithmsConsumer) {
return this;
}
@Override
public Builder verificationX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer) {
return this;
}
@Override
public Builder encryptionX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer) {
return this;
}
@Override
public Builder singleSignOnServiceLocation(String singleSignOnServiceLocation) {
return this;
}
@Override
public Builder singleSignOnServiceBinding(Saml2MessageBinding singleSignOnServiceBinding) {
return this;
}
@Override
public Builder singleLogoutServiceLocation(String singleLogoutServiceLocation) {
return this;
}
@Override
public Builder singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation) {
return this;
}
@Override
public Builder singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding) {
return this;
}
@Override
public AssertingPartyMetadata build() {
return new CustomAssertingPartyMetadata();
}
}
}
}