diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java index a6f3f9d9c2..973e4bb7b5 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java @@ -57,7 +57,7 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication @Override public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) { AuthnRequest authnRequest = createAuthnRequest(context); - String xml = context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest() ? + String xml = context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned() ? this.saml.serialize(authnRequest, context.getRelyingPartyRegistration().getSigningCredentials()) : this.saml.serialize(authnRequest); @@ -78,7 +78,7 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication result.samlRequest(deflatedAndEncoded) .relayState(context.getRelayState()); - if (context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest()) { + if (context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()) { List signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials(); Map signedParams = this.saml.signQueryParameters( signingCredentials, diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java index fc79da8d73..01343f2b3c 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java @@ -91,7 +91,7 @@ public class Saml2AuthenticationRequestContext { * @return the Destination value */ public String getDestination() { - return this.getRelyingPartyRegistration().getProviderDetails().getWebSsoUrl(); + return this.getRelyingPartyRegistration().getAssertingPartyDetails().getSingleSignOnServiceLocation(); } /** diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java index f96d492ed9..14c29c23d1 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java @@ -16,125 +16,178 @@ package org.springframework.security.saml2.provider.service.registration; -import org.springframework.security.saml2.credentials.Saml2X509Credential; -import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType; -import org.springframework.util.Assert; - import java.util.Collection; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; -import static java.util.Collections.unmodifiableList; -import static org.springframework.util.Assert.hasText; -import static org.springframework.util.Assert.notEmpty; -import static org.springframework.util.Assert.notNull; +import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType; +import org.springframework.util.Assert; /** - * Represents a configured service provider, SP, and a remote identity provider, IDP, pair. - * Each SP/IDP pair is uniquely identified using a registrationId, an arbitrary string. - * A fully configured registration may look like + * Represents a configured relying party (aka Service Provider) and asserting party (aka Identity Provider) pair. + * + *

+ * Each RP/AP pair is uniquely identified using a {@code registrationId}, an arbitrary string. + * + *

+ * A fully configured registration may look like: + * *

- *		//remote IDP entity ID
- *		String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
- *		//remote WebSSO Endpoint - Where to Send AuthNRequests to
- *		String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
- *		//local registration ID
- *		String registrationId = "simplesamlphp";
- *		//local entity ID - autogenerated based on URL
- *		String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
- *		//local SSO URL - autogenerated, endpoint to receive SAML Response objects
- *		String acsUrlTemplate = "{baseUrl}/login/saml2/sso/{registrationId}";
- *		//local signing (and local decryption key and remote encryption certificate)
- *		Saml2X509Credential signingCredential = getSigningCredential();
- *		//IDP certificate for verification of incoming messages
- *		Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();
- *		RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(registrationId)
- * 				.providerDetails(config -> config.entityId(idpEntityId));
- * 				.providerDetails(config -> config.webSsoUrl(url));
- * 				.credentials(c -> c.add(signingCredential))
- * 				.credentials(c -> c.add(idpVerificationCertificate))
- * 				.localEntityIdTemplate(localEntityIdTemplate)
- * 				.assertionConsumerServiceUrlTemplate(acsUrlTemplate)
- * 				.build();
+ *	String registrationId = "simplesamlphp";
+ *
+ * 	String relyingPartyEntityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
+ *	String assertionConsumerServiceLocation = "{baseUrl}/login/saml2/sso/{registrationId}";
+ *	Saml2X509Credential relyingPartySigningCredential = ...;
+ *
+ *	String assertingPartyEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
+ *	String singleSignOnServiceLocation = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
+ * 	Saml2X509Credential assertingPartyVerificationCredential = ...;
+ *
+ *
+ *	RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(registrationId)
+ * 			.entityId(relyingPartyEntityId)
+ * 			.assertionConsumerServiceLocation(assertingConsumerServiceLocation)
+ * 		 	.credentials(c -> c.add(relyingPartySigningCredential))
+ * 			.assertingPartyDetails(details -> details
+ * 				.entityId(assertingPartyEntityId));
+ * 				.singleSignOnServiceLocation(singleSignOnServiceLocation))
+ * 				.credentials(c -> c.add(assertingPartyVerificationCredential))
+ * 			.build();
  * 
+ * * @since 5.2 + * @author Filip Hanik + * @author Josh Cummings */ public class RelyingPartyRegistration { private final String registrationId; - private final String assertionConsumerServiceUrlTemplate; - private final List credentials; - private final String localEntityIdTemplate; + private final String entityId; + private final String assertionConsumerServiceLocation; private final ProviderDetails providerDetails; + private final List credentials; private RelyingPartyRegistration( String registrationId, - String assertionConsumerServiceUrlTemplate, + String entityId, + String assertionConsumerServiceLocation, ProviderDetails providerDetails, - List credentials, - String localEntityIdTemplate) { - hasText(registrationId, "registrationId cannot be empty"); - hasText(assertionConsumerServiceUrlTemplate, "assertionConsumerServiceUrlTemplate cannot be empty"); - hasText(localEntityIdTemplate, "localEntityIdTemplate cannot be empty"); - notEmpty(credentials, "credentials cannot be empty"); - notNull(providerDetails, "providerDetails cannot be null"); - hasText(providerDetails.webSsoUrl, "providerDetails.webSsoUrl cannot be empty"); + List credentials) { + + Assert.hasText(registrationId, "registrationId cannot be empty"); + Assert.hasText(entityId, "entityId cannot be empty"); + Assert.hasText(assertionConsumerServiceLocation, "assertionConsumerServiceLocation cannot be empty"); + Assert.notNull(providerDetails, "providerDetails cannot be null"); + Assert.notEmpty(credentials, "credentials cannot be empty"); for (Saml2X509Credential c : credentials) { - notNull(c, "credentials cannot contain null elements"); + Assert.notNull(c, "credentials cannot contain null elements"); } this.registrationId = registrationId; - this.assertionConsumerServiceUrlTemplate = assertionConsumerServiceUrlTemplate; - this.credentials = unmodifiableList(new LinkedList<>(credentials)); + this.entityId = entityId; + this.assertionConsumerServiceLocation = assertionConsumerServiceLocation; this.providerDetails = providerDetails; - this.localEntityIdTemplate = localEntityIdTemplate; + this.credentials = Collections.unmodifiableList(new LinkedList<>(credentials)); + } + + /** + * Get the unique registration id for this RP/AP pair + * + * @return the unique registration id for this RP/AP pair + */ + public String getRegistrationId() { + return this.registrationId; + } + + /** + * Get the relying party's + * EntityID. + * + *

+ * Equivalent to the value found in the relying party's + * <EntityDescriptor EntityID="..."/> + * + *

+ * This value may contain a number of placeholders, which need to be + * resolved before use. They are {@code baseUrl}, {@code registrationId}, + * {@code baseScheme}, {@code baseHost}, and {@code basePort}. + * + * @return the relying party's EntityID + * @since 5.4 + */ + public String getEntityId() { + return this.entityId; + } + + /** + * Get the AssertionConsumerService Location. + * Equivalent to the value found in <AssertionConsumerService Location="..."/> + * in the relying party's <SPSSODescriptor>. + * + * This value may contain a number of placeholders, which need to be + * resolved before use. They are {@code baseUrl}, {@code registrationId}, + * {@code baseScheme}, {@code baseHost}, and {@code basePort}. + * + * @return the AssertionConsumerService Location + * @since 5.4 + */ + public String getAssertionConsumerServiceLocation() { + return this.assertionConsumerServiceLocation; + } + + /** + * Get the configuration details for the Asserting Party + * + * @return the {@link AssertingPartyDetails} + * @since 5.4 + */ + public AssertingPartyDetails getAssertingPartyDetails() { + return this.providerDetails.assertingPartyDetails; } /** * Returns the entity ID of the IDP, the asserting party. * @return entity ID of the asserting party - * @deprecated use {@link ProviderDetails#getEntityId()} from {@link #getProviderDetails()} + * @deprecated use {@link AssertingPartyDetails#getEntityId} from {@link #getAssertingPartyDetails} */ @Deprecated public String getRemoteIdpEntityId() { return this.providerDetails.getEntityId(); } - /** - * Returns the unique relying party registration ID - * @return registrationId - */ - public String getRegistrationId() { - return this.registrationId; - } - /** * returns the URL template for which ACS URL authentication requests should contain * Possible variables are {@code baseUrl}, {@code registrationId}, * {@code baseScheme}, {@code baseHost}, and {@code basePort}. * @return string containing the ACS URL template, with or without variables present + * @deprecated Use {@link #getAssertionConsumerServiceLocation} instead */ + @Deprecated public String getAssertionConsumerServiceUrlTemplate() { - return this.assertionConsumerServiceUrlTemplate; + return this.assertionConsumerServiceLocation; } /** * Contains the URL for which to send the SAML 2 Authentication Request to initiate * a single sign on flow. * @return a IDP URL that accepts REDIRECT or POST binding for authentication requests - * @deprecated use {@link ProviderDetails#getWebSsoUrl()} from {@link #getProviderDetails()} + * @deprecated use {@link AssertingPartyDetails#getSingleSignOnServiceLocation} from {@link #getAssertingPartyDetails} */ @Deprecated public String getIdpWebSsoUrl() { - return this.getProviderDetails().webSsoUrl; + return this.getAssertingPartyDetails().getSingleSignOnServiceLocation(); } /** * Returns specific configuration around the Identity Provider SSO endpoint * @return the IDP SSO endpoint configuration * @since 5.3 + * @deprecated Use {@link #getAssertingPartyDetails} instead */ + @Deprecated public ProviderDetails getProviderDetails() { return this.providerDetails; } @@ -145,9 +198,11 @@ public class RelyingPartyRegistration { * {@code baseScheme}, {@code baseHost}, and {@code basePort}, for example * {@code {baseUrl}/saml2/service-provider-metadata/{registrationId}} * @return a string containing the entity ID or entity ID template + * @deprecated Use {@link #getEntityId} instead */ + @Deprecated public String getLocalEntityIdTemplate() { - return this.localEntityIdTemplate; + return this.entityId; } /** @@ -223,40 +278,196 @@ public class RelyingPartyRegistration { public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) { Assert.notNull(registration, "registration cannot be null"); return withRegistrationId(registration.getRegistrationId()) - .providerDetails(c -> { - c.webSsoUrl(registration.getProviderDetails().getWebSsoUrl()); - c.binding(registration.getProviderDetails().getBinding()); - c.signAuthNRequest(registration.getProviderDetails().isSignAuthNRequest()); - c.entityId(registration.getProviderDetails().getEntityId()); - }) - .credentials(c -> c.addAll(registration.getCredentials())) - .localEntityIdTemplate(registration.getLocalEntityIdTemplate()) - .assertionConsumerServiceUrlTemplate(registration.getAssertionConsumerServiceUrlTemplate()) - ; + .entityId(registration.getEntityId()) + .assertionConsumerServiceLocation(registration.getAssertionConsumerServiceLocation()) + .assertingPartyDetails(c -> c + .entityId(registration.getAssertingPartyDetails().getEntityId()) + .wantAuthnRequestsSigned(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) + .singleSignOnServiceLocation(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()) + .singleSignOnServiceBinding(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + ) + .credentials(c -> c.addAll(registration.getCredentials())); + } + + + /** + * The configuration metadata of the Asserting party + * + * @since 5.4 + */ + public final static class AssertingPartyDetails { + private final String entityId; + private final boolean wantAuthnRequestsSigned; + private final String singleSignOnServiceLocation; + private final Saml2MessageBinding singleSignOnServiceBinding; + + private AssertingPartyDetails( + String entityId, + boolean wantAuthnRequestsSigned, + String singleSignOnServiceLocation, + Saml2MessageBinding singleSignOnServiceBinding) { + + Assert.hasText(entityId, "entityId cannot be null or empty"); + Assert.notNull(singleSignOnServiceLocation, "singleSignOnServiceLocation cannot be null"); + Assert.notNull(singleSignOnServiceBinding, "singleSignOnServiceBinding cannot be null"); + this.entityId = entityId; + this.wantAuthnRequestsSigned = wantAuthnRequestsSigned; + this.singleSignOnServiceLocation = singleSignOnServiceLocation; + this.singleSignOnServiceBinding = singleSignOnServiceBinding; + } + + /** + * Get the asserting party's + * EntityID. + * + *

+ * Equivalent to the value found in the asserting party's + * <EntityDescriptor EntityID="..."/> + * + *

+ * This value may contain a number of placeholders, which need to be + * resolved before use. They are {@code baseUrl}, {@code registrationId}, + * {@code baseScheme}, {@code baseHost}, and {@code basePort}. + * + * @return the asserting party's EntityID + */ + public String getEntityId() { + return this.entityId; + } + + /** + * Get the WantAuthnRequestsSigned setting, indicating the asserting party's preference that + * relying parties should sign the AuthnRequest before sending. + * + * @return the WantAuthnRequestsSigned value + */ + public boolean getWantAuthnRequestsSigned() { + return this.wantAuthnRequestsSigned; + } + + /** + * Get the + * SingleSignOnService + * Location. + * + *

+ * Equivalent to the value found in <SingleSignOnService Location="..."/> + * in the asserting party's <IDPSSODescriptor>. + * + * @return the SingleSignOnService Location + */ + public String getSingleSignOnServiceLocation() { + return this.singleSignOnServiceLocation; + } + + /** + * Get the + * SingleSignOnService + * Binding. + * + *

+ * Equivalent to the value found in <SingleSignOnService Binding="..."/> + * in the asserting party's <IDPSSODescriptor>. + * + * @return the SingleSignOnService Location + */ + public Saml2MessageBinding getSingleSignOnServiceBinding() { + return this.singleSignOnServiceBinding; + } + + public final static class Builder { + private String entityId; + private boolean wantAuthnRequestsSigned = true; + private String singleSignOnServiceLocation; + private Saml2MessageBinding singleSignOnServiceBinding = Saml2MessageBinding.REDIRECT; + + /** + * Set the asserting party's + * EntityID. + * Equivalent to the value found in the asserting party's + * <EntityDescriptor EntityID="..."/> + * + * @param entityId the asserting party's EntityID + * @return the {@link ProviderDetails.Builder} for further configuration + */ + public Builder entityId(String entityId) { + this.entityId = entityId; + return this; + } + + /** + * Set the WantAuthnRequestsSigned setting, indicating the asserting party's preference that + * relying parties should sign the AuthnRequest before sending. + * + * @param wantAuthnRequestsSigned the WantAuthnRequestsSigned setting + * @return the {@link ProviderDetails.Builder} for further configuration + */ + public Builder wantAuthnRequestsSigned(boolean wantAuthnRequestsSigned) { + this.wantAuthnRequestsSigned = wantAuthnRequestsSigned; + return this; + } + + /** + * Set the + * SingleSignOnService + * Location. + * + *

+ * Equivalent to the value found in <SingleSignOnService Location="..."/> + * in the asserting party's <IDPSSODescriptor>. + * + * @param singleSignOnServiceLocation the SingleSignOnService Location + * @return the {@link ProviderDetails.Builder} for further configuration + */ + public Builder singleSignOnServiceLocation(String singleSignOnServiceLocation) { + this.singleSignOnServiceLocation = singleSignOnServiceLocation; + return this; + } + + /** + * Set the + * SingleSignOnService + * Binding. + * + *

+ * Equivalent to the value found in <SingleSignOnService Binding="..."/> + * in the asserting party's <IDPSSODescriptor>. + * + * @param singleSignOnServiceBinding the SingleSignOnService Binding + * @return the {@link ProviderDetails.Builder} for further configuration + */ + public Builder singleSignOnServiceBinding(Saml2MessageBinding singleSignOnServiceBinding) { + this.singleSignOnServiceBinding = singleSignOnServiceBinding; + return this; + } + + /** + * Creates an immutable ProviderDetails object representing the configuration for an Identity Provider, IDP + * @return immutable ProviderDetails object + */ + public AssertingPartyDetails build() { + return new AssertingPartyDetails( + this.entityId, + this.wantAuthnRequestsSigned, + this.singleSignOnServiceLocation, + this.singleSignOnServiceBinding + ); + } + } } /** * Configuration for IDP SSO endpoint configuration * @since 5.3 + * @deprecated Use {@link AssertingPartyDetails} instead */ + @Deprecated public final static class ProviderDetails { - private final String entityId; - private final String webSsoUrl; - private final boolean signAuthNRequest; - private final Saml2MessageBinding binding; + private final AssertingPartyDetails assertingPartyDetails; - private ProviderDetails( - String entityId, - String webSsoUrl, - boolean signAuthNRequest, - Saml2MessageBinding binding) { - hasText(entityId, "entityId cannot be null or empty"); - notNull(webSsoUrl, "webSsoUrl cannot be null"); - notNull(binding, "binding cannot be null"); - this.entityId = entityId; - this.webSsoUrl = webSsoUrl; - this.signAuthNRequest = signAuthNRequest; - this.binding = binding; + private ProviderDetails(AssertingPartyDetails assertingPartyDetails) { + Assert.notNull("assertingPartyDetails cannot be null"); + this.assertingPartyDetails = assertingPartyDetails; } /** @@ -264,7 +475,7 @@ public class RelyingPartyRegistration { * @return the entity ID of the IDP */ public String getEntityId() { - return entityId; + return this.assertingPartyDetails.getEntityId(); } /** @@ -273,7 +484,7 @@ public class RelyingPartyRegistration { * @return a IDP URL that accepts REDIRECT or POST binding for authentication requests */ public String getWebSsoUrl() { - return webSsoUrl; + return this.assertingPartyDetails.getSingleSignOnServiceLocation(); } /** @@ -281,34 +492,38 @@ public class RelyingPartyRegistration { * {@code false} if no signature is required. */ public boolean isSignAuthNRequest() { - return signAuthNRequest; + return this.assertingPartyDetails.getWantAuthnRequestsSigned(); } /** * @return the type of SAML 2 Binding the AuthNRequest should be sent on */ public Saml2MessageBinding getBinding() { - return binding; + return this.assertingPartyDetails.getSingleSignOnServiceBinding(); } /** * Builder for IDP SSO endpoint configuration * @since 5.3 + * @deprecated Use {@link AssertingPartyDetails.Builder} instead */ + @Deprecated public final static class Builder { - private String entityId; - private String webSsoUrl; - private boolean signAuthNRequest = true; - private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT; + private final AssertingPartyDetails.Builder assertingPartyDetailsBuilder = + new AssertingPartyDetails.Builder(); /** - * Sets the {@code EntityID} for the remote asserting party, the Identity Provider. + * Set the asserting party's + * EntityID. + * Equivalent to the value found in the asserting party's + * <EntityDescriptor EntityID="..."/> * - * @param entityId - the EntityID of the IDP. May be a URL. - * @return this object + * @param entityId the asserting party's EntityID + * @return the {@link Builder} for further configuration + * @since 5.4 */ public Builder entityId(String entityId) { - this.entityId = entityId; + this.assertingPartyDetailsBuilder.entityId(entityId); return this; } @@ -319,7 +534,7 @@ public class RelyingPartyRegistration { * @return this object */ public Builder webSsoUrl(String url) { - this.webSsoUrl = url; + this.assertingPartyDetailsBuilder.singleSignOnServiceLocation(url); return this; } @@ -330,7 +545,7 @@ public class RelyingPartyRegistration { * @return this object */ public Builder signAuthNRequest(boolean signAuthNRequest) { - this.signAuthNRequest = signAuthNRequest; + this.assertingPartyDetailsBuilder.wantAuthnRequestsSigned(signAuthNRequest); return this; } @@ -342,7 +557,7 @@ public class RelyingPartyRegistration { * @return this object */ public Builder binding(Saml2MessageBinding binding) { - this.binding = binding; + this.assertingPartyDetailsBuilder.singleSignOnServiceBinding(binding); return this; } @@ -351,22 +566,17 @@ public class RelyingPartyRegistration { * @return immutable ProviderDetails object */ public ProviderDetails build() { - return new ProviderDetails( - this.entityId, - this.webSsoUrl, - this.signAuthNRequest, - this.binding - ); + return new ProviderDetails(this.assertingPartyDetailsBuilder.build()); } } } public final static class Builder { private String registrationId; - private String assertionConsumerServiceUrlTemplate; - private List credentials = new LinkedList<>(); - private String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; + private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; + private String assertionConsumerServiceLocation; private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder(); + private List credentials = new LinkedList<>(); private Builder(String registrationId) { this.registrationId = registrationId; @@ -384,49 +594,54 @@ public class RelyingPartyRegistration { } /** - * Sets the {@code entityId} for the remote asserting party, the Identity Provider. - * @param entityId the IDP entityId - * @return this object - * @deprecated use {@link #providerDetails(Consumer< ProviderDetails.Builder >)} - */ - @Deprecated - public Builder remoteIdpEntityId(String entityId) { - this.providerDetails(idp -> idp.entityId(entityId)); - return this; - } - - /** - * Assertion Consumer - * Service URL template. It can contain variables {@code baseUrl}, {@code registrationId}, + * Set the relying party's + * EntityID. + * Equivalent to the value found in the relying party's + * <EntityDescriptor EntityID="..."/> + * + * This value may contain a number of placeholders. + * They are {@code baseUrl}, {@code registrationId}, * {@code baseScheme}, {@code baseHost}, and {@code basePort}. - * @param assertionConsumerServiceUrlTemplate the Assertion Consumer Service URL template (i.e. - * "{baseUrl}/login/saml2/sso/{registrationId}". - * @return this object + * + * @return the {@link Builder} for further configuration + * @since 5.4 */ - public Builder assertionConsumerServiceUrlTemplate(String assertionConsumerServiceUrlTemplate) { - this.assertionConsumerServiceUrlTemplate = assertionConsumerServiceUrlTemplate; + public Builder entityId(String entityId) { + this.entityId = entityId; return this; } /** - * Sets the {@code SSO URL} for the remote asserting party, the Identity Provider. - * @param url - a URL that accepts authentication requests via REDIRECT or POST bindings - * @return this object - * @deprecated use {@link #providerDetails(Consumer< ProviderDetails.Builder >)} + * Set the AssertionConsumerService + * Location. + * + *

+ * Equivalent to the value found in <AssertionConsumerService Location="..."/> + * in the relying party's <SPSSODescriptor> + * + *

+ * This value may contain a number of placeholders. + * They are {@code baseUrl}, {@code registrationId}, + * {@code baseScheme}, {@code baseHost}, and {@code basePort}. + * + * @param assertionConsumerServiceLocation + * @return the {@link Builder} for further configuration + * @since 5.4 */ - @Deprecated - public Builder idpWebSsoUrl(String url) { - providerDetails(config -> config.webSsoUrl(url)); + public Builder assertionConsumerServiceLocation(String assertionConsumerServiceLocation) { + this.assertionConsumerServiceLocation = assertionConsumerServiceLocation; return this; } /** - * Configures the IDP SSO endpoint - * @param providerDetails a consumer that configures the IDP SSO endpoint - * @return this object + * Apply this {@link Consumer} to further configure the Asserting Party details + * + * @param assertingPartyDetails The {@link Consumer} to apply + * @return the {@link Builder} for further configuration + * @since 5.4 */ - public Builder providerDetails(Consumer providerDetails) { - providerDetails.accept(this.providerDetails); + public Builder assertingPartyDetails(Consumer assertingPartyDetails) { + assertingPartyDetails.accept(this.providerDetails.assertingPartyDetailsBuilder); return this; } @@ -449,16 +664,68 @@ public class RelyingPartyRegistration { return this; } + /** + * Assertion Consumer + * Service URL template. It can contain variables {@code baseUrl}, {@code registrationId}, + * {@code baseScheme}, {@code baseHost}, and {@code basePort}. + * @param assertionConsumerServiceUrlTemplate the Assertion Consumer Service URL template (i.e. + * "{baseUrl}/login/saml2/sso/{registrationId}". + * @return this object + * @deprecated Use {@link #assertionConsumerServiceLocation} instead. + */ + @Deprecated + public Builder assertionConsumerServiceUrlTemplate(String assertionConsumerServiceUrlTemplate) { + this.assertionConsumerServiceLocation = assertionConsumerServiceUrlTemplate; + return this; + } + + /** + * Sets the {@code entityId} for the remote asserting party, the Identity Provider. + * @param entityId the IDP entityId + * @return this object + * @deprecated use {@link #assertingPartyDetails(Consumer< AssertingPartyDetails.Builder >)} + */ + @Deprecated + public Builder remoteIdpEntityId(String entityId) { + assertingPartyDetails(idp -> idp.entityId(entityId)); + return this; + } + + /** + * Sets the {@code SSO URL} for the remote asserting party, the Identity Provider. + * @param url - a URL that accepts authentication requests via REDIRECT or POST bindings + * @return this object + * @deprecated use {@link #assertingPartyDetails(Consumer< AssertingPartyDetails.Builder >)} + */ + @Deprecated + public Builder idpWebSsoUrl(String url) { + assertingPartyDetails(config -> config.singleSignOnServiceLocation(url)); + return this; + } + /** * Sets the local relying party, or Service Provider, entity Id template. * can generate it's entity ID based on possible variables of {@code baseUrl}, {@code registrationId}, * {@code baseScheme}, {@code baseHost}, and {@code basePort}, for example * {@code {baseUrl}/saml2/service-provider-metadata/{registrationId}} * @return a string containing the entity ID or entity ID template + * @deprecated Use {@link #entityId} instead */ - + @Deprecated public Builder localEntityIdTemplate(String template) { - this.localEntityIdTemplate = template; + this.entityId = template; + return this; + } + + /** + * Configures the IDP SSO endpoint + * @param providerDetails a consumer that configures the IDP SSO endpoint + * @return this object + * @deprecated Use {@link #assertingPartyDetails} instead + */ + @Deprecated + public Builder providerDetails(Consumer providerDetails) { + providerDetails.accept(this.providerDetails); return this; } @@ -469,10 +736,10 @@ public class RelyingPartyRegistration { public RelyingPartyRegistration build() { return new RelyingPartyRegistration( this.registrationId, - this.assertionConsumerServiceUrlTemplate, + this.entityId, + this.assertionConsumerServiceLocation, this.providerDetails.build(), - this.credentials, - this.localEntityIdTemplate + this.credentials ); } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java index 705acf6c0f..91515b6a3a 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java @@ -40,7 +40,7 @@ final class Saml2ServletUtils { return baseUrl; } - String entityId = relyingParty.getProviderDetails().getEntityId(); + String entityId = relyingParty.getAssertingPartyDetails().getEntityId(); String registrationId = relyingParty.getRegistrationId(); Map uriVariables = new HashMap<>(); UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java index 2e3d9ddb95..028a07973a 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java @@ -98,11 +98,11 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce throw new Saml2AuthenticationException(saml2Error); } String applicationUri = Saml2ServletUtils.getApplicationUri(request); - String localSpEntityId = Saml2ServletUtils.resolveUrlTemplate(rp.getLocalEntityIdTemplate(), applicationUri, rp); + String localSpEntityId = Saml2ServletUtils.resolveUrlTemplate(rp.getEntityId(), applicationUri, rp); final Saml2AuthenticationToken authentication = new Saml2AuthenticationToken( responseXml, request.getRequestURL().toString(), - rp.getProviderDetails().getEntityId(), + rp.getAssertingPartyDetails().getEntityId(), localSpEntityId, rp.getCredentials() ); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java index 623a04cbba..f0270de213 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java @@ -16,6 +16,12 @@ package org.springframework.security.saml2.provider.service.servlet.filter; +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.springframework.http.MediaType; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory; @@ -36,12 +42,6 @@ import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - import static java.nio.charset.StandardCharsets.ISO_8859_1; /** @@ -155,7 +155,7 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter return; } Saml2AuthenticationRequestContext context = authenticationRequestContextResolver.resolve(request, relyingParty); - if (relyingParty.getProviderDetails().getBinding() == Saml2MessageBinding.REDIRECT) { + if (relyingParty.getAssertingPartyDetails().getSingleSignOnServiceBinding() == Saml2MessageBinding.REDIRECT) { sendRedirect(response, context); } else { sendPost(response, context); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java index eae28e6d03..7910b74bb9 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java @@ -67,8 +67,8 @@ public final class DefaultSaml2AuthenticationRequestContextResolver implements S String applicationUri = getApplicationUri(request); Function resolver = templateResolver(applicationUri, relyingParty); - String localSpEntityId = resolver.apply(relyingParty.getLocalEntityIdTemplate()); - String assertionConsumerServiceUrl = resolver.apply(relyingParty.getAssertionConsumerServiceUrlTemplate()); + String localSpEntityId = resolver.apply(relyingParty.getEntityId()); + String assertionConsumerServiceUrl = resolver.apply(relyingParty.getAssertionConsumerServiceLocation()); return Saml2AuthenticationRequestContext.builder() .issuer(localSpEntityId) .relyingPartyRegistration(relyingParty) @@ -82,7 +82,7 @@ public final class DefaultSaml2AuthenticationRequestContextResolver implements S } private static String resolveUrlTemplate(String template, String baseUrl, RelyingPartyRegistration relyingParty) { - String entityId = relyingParty.getProviderDetails().getEntityId(); + String entityId = relyingParty.getAssertingPartyDetails().getEntityId(); String registrationId = relyingParty.getRegistrationId(); Map uriVariables = new HashMap<>(); UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl) diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java index fac6b53cc8..c4c5db23fd 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java @@ -52,7 +52,7 @@ public class OpenSamlAuthenticationRequestFactoryTests { @Before public void setUp() { relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("id") - .assertionConsumerServiceUrlTemplate("template") + .assertionConsumerServiceLocation("template") .providerDetails(c -> c.webSsoUrl("https://destination/sso")) .providerDetails(c -> c.entityId("remote-entity-id")) .localEntityIdTemplate("local-entity-id") diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java index 63ee9d55d4..cdf99d7d71 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java @@ -42,9 +42,13 @@ public class RelyingPartyRegistrationTests { .isEqualTo("simplesamlphp"); assertThat(copy.getProviderDetails().getEntityId()) .isEqualTo(registration.getProviderDetails().getEntityId()) + .isEqualTo(copy.getAssertingPartyDetails().getEntityId()) + .isEqualTo(registration.getAssertingPartyDetails().getEntityId()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"); assertThat(copy.getAssertionConsumerServiceUrlTemplate()) .isEqualTo(registration.getAssertionConsumerServiceUrlTemplate()) + .isEqualTo(copy.getAssertionConsumerServiceLocation()) + .isEqualTo(registration.getAssertionConsumerServiceLocation()) .isEqualTo("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); assertThat(copy.getCredentials()) .containsAll(registration.getCredentials()) @@ -54,15 +58,23 @@ public class RelyingPartyRegistrationTests { ); assertThat(copy.getLocalEntityIdTemplate()) .isEqualTo(registration.getLocalEntityIdTemplate()) + .isEqualTo(copy.getEntityId()) + .isEqualTo(registration.getEntityId()) .isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); assertThat(copy.getProviderDetails().getWebSsoUrl()) .isEqualTo(registration.getProviderDetails().getWebSsoUrl()) + .isEqualTo(copy.getAssertingPartyDetails().getSingleSignOnServiceLocation()) + .isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"); assertThat(copy.getProviderDetails().getBinding()) .isEqualTo(registration.getProviderDetails().getBinding()) + .isEqualTo(copy.getAssertingPartyDetails().getSingleSignOnServiceBinding()) + .isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) .isEqualTo(POST); assertThat(copy.getProviderDetails().isSignAuthNRequest()) .isEqualTo(registration.getProviderDetails().isSignAuthNRequest()) + .isEqualTo(copy.getAssertingPartyDetails().getWantAuthnRequestsSigned()) + .isEqualTo(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) .isFalse(); } } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java index d8c9686d41..5aa604610c 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java @@ -28,26 +28,24 @@ import static org.springframework.security.saml2.credentials.TestSaml2X509Creden public class TestRelyingPartyRegistrations { public static RelyingPartyRegistration.Builder relyingPartyRegistration() { - //remote IDP entity ID - String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"; - //remote WebSSO Endpoint - Where to Send AuthNRequests to - String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"; - //local registration ID String registrationId = "simplesamlphp"; - //local entity ID - autogenerated based on URL - String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; - //local signing (and decryption key) + + String rpEntityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; Saml2X509Credential signingCredential = relyingPartySigningCredential(); - //IDP certificate for verification of incoming messages - Saml2X509Credential idpVerificationCertificate = relyingPartyVerifyingCredential(); - String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI; + String assertionConsumerServiceLocation = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI; + + String apEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"; + Saml2X509Credential verificationCertificate = relyingPartyVerifyingCredential(); + String singleSignOnServiceLocation = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"; + return RelyingPartyRegistration.withRegistrationId(registrationId) - .providerDetails(c -> c.entityId(idpEntityId)) - .providerDetails(c -> c.webSsoUrl(webSsoEndpoint)) + .entityId(rpEntityId) + .assertionConsumerServiceLocation(assertionConsumerServiceLocation) .credentials(c -> c.add(signingCredential)) - .credentials(c -> c.add(idpVerificationCertificate)) - .localEntityIdTemplate(localEntityIdTemplate) - .assertionConsumerServiceUrlTemplate(acsUrlTemplate); + .providerDetails(c -> c + .entityId(apEntityId) + .webSsoUrl(singleSignOnServiceLocation)) + .credentials(c -> c.add(verificationCertificate)); } diff --git a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index 6acdca5bd6..ddc28ca4af 100644 --- a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -15,6 +15,12 @@ */ package org.springframework.security.samples.config; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -25,12 +31,6 @@ import org.springframework.security.saml2.provider.service.registration.InMemory import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING; import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION; @@ -54,12 +54,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { Saml2X509Credential idpVerificationCertificate = getVerificationCertificate(); String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI; return RelyingPartyRegistration.withRegistrationId(registrationId) - .providerDetails(config -> config.entityId(idpEntityId)) - .providerDetails(config -> config.webSsoUrl(webSsoEndpoint)) + .entityId(localEntityIdTemplate) + .assertionConsumerServiceLocation(acsUrlTemplate) .credentials(c -> c.add(signingCredential)) - .credentials(c -> c.add(idpVerificationCertificate)) - .localEntityIdTemplate(localEntityIdTemplate) - .assertionConsumerServiceUrlTemplate(acsUrlTemplate) + .assertingPartyDetails(config -> config + .entityId(idpEntityId) + .singleSignOnServiceLocation(webSsoEndpoint)) + .credentials(c -> c.add(idpVerificationCertificate)) .build(); }