From c330e1f90ee93a6b40af2a26485a1ec46cc08c69 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 1 Jul 2020 11:29:32 -0600 Subject: [PATCH] Use Spec Language in RelyingPartyRegistration Changed conventions to better follow the metadata descriptors that the registration is meant to represent. Closes gh-8777 --- .../OpenSamlAuthenticationRequestFactory.java | 4 +- .../Saml2AuthenticationRequestContext.java | 2 +- .../RelyingPartyRegistration.java | 472 +++++++++++++----- .../Saml2WebSsoAuthenticationFilter.java | 2 +- ...aml2WebSsoAuthenticationRequestFilter.java | 14 +- ...2AuthenticationRequestContextResolver.java | 4 +- ...SamlAuthenticationRequestFactoryTests.java | 2 +- .../RelyingPartyRegistrationTests.java | 10 + .../TestRelyingPartyRegistrations.java | 30 +- .../samples/config/SecurityConfig.java | 18 +- 10 files changed, 387 insertions(+), 171 deletions(-) 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 a6f3f9d9c25..b7afe9491d9 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 String createAuthenticationRequest(Saml2AuthenticationRequest request) { @Override public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) { AuthnRequest authnRequest = createAuthnRequest(context); - String xml = context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest() ? + String xml = context.getRelyingPartyRegistration().getProviderDetails().getWantsAuthnRequestsSigned() ? this.saml.serialize(authnRequest, context.getRelyingPartyRegistration().getSigningCredentials()) : this.saml.serialize(authnRequest); @@ -78,7 +78,7 @@ public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Sa result.samlRequest(deflatedAndEncoded) .relayState(context.getRelayState()); - if (context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest()) { + if (context.getRelyingPartyRegistration().getProviderDetails().getWantsAuthnRequestsSigned()) { 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 fc79da8d73b..7c057f47956 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 String getRelayState() { * @return the Destination value */ public String getDestination() { - return this.getRelyingPartyRegistration().getProviderDetails().getWebSsoUrl(); + return this.getRelyingPartyRegistration().getProviderDetails().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 f96d492ed94..4f4b8a7ca3a 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,79 +16,128 @@ 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))
+ * 			.providerDetails(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 String assertionConsumerServiceLocation; private final List credentials; - private final String localEntityIdTemplate; + private final String entityId; private final ProviderDetails providerDetails; 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.hasText(providerDetails.singleSignOnServiceLocation, "providerDetails.webSsoUrl cannot be empty"); + 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; } /** @@ -101,22 +150,16 @@ 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; } /** @@ -127,7 +170,7 @@ public String getAssertionConsumerServiceUrlTemplate() { */ @Deprecated public String getIdpWebSsoUrl() { - return this.getProviderDetails().webSsoUrl; + return this.getProviderDetails().getSingleSignOnServiceLocation(); } /** @@ -145,9 +188,11 @@ public ProviderDetails getProviderDetails() { * {@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,16 +268,15 @@ public static Builder withRegistrationId(String registrationId) { 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.getAssertionConsumerServiceUrlTemplate()) + .providerDetails(c -> c + .entityId(registration.getProviderDetails().getEntityId()) + .wantsAuthnRequestsSigned(registration.getProviderDetails().getWantsAuthnRequestsSigned()) + .singleSignOnServiceLocation(registration.getProviderDetails().getSingleSignOnServiceLocation()) + .singleSignOnServiceBinding(registration.getProviderDetails().getSingleSignOnServiceBinding()) + ) + .credentials(c -> c.addAll(registration.getCredentials())); } /** @@ -241,54 +285,116 @@ public static Builder withRelyingPartyRegistration(RelyingPartyRegistration regi */ public final static class ProviderDetails { private final String entityId; - private final String webSsoUrl; + private final String singleSignOnServiceLocation; + private final Saml2MessageBinding singleSignOnServiceBinding; private final boolean signAuthNRequest; - private final Saml2MessageBinding binding; 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"); + boolean wantsAuthnRequestsSigned, + 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.webSsoUrl = webSsoUrl; - this.signAuthNRequest = signAuthNRequest; - this.binding = binding; + this.signAuthNRequest = wantsAuthnRequestsSigned; + this.singleSignOnServiceLocation = singleSignOnServiceLocation; + this.singleSignOnServiceBinding = singleSignOnServiceBinding; } /** - * Returns the entity ID of the Identity Provider - * @return the entity ID of the IDP + * 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 relying party's EntityID + * @since 5.4 */ public String getEntityId() { - return entityId; + return this.entityId; + } + + /** + * Get the WantsAuthnRequestsSigned setting, indicating the asserting party's preference that + * relying parties should sign the AuthnRequest before sending. + * + * @return the WantsAuthnRequestsSigned value + * @since 5.4 + */ + public boolean getWantsAuthnRequestsSigned() { + return this.signAuthNRequest; + } + + /** + * Get the + * SingleSignOnService + * Location. + * + *

+ * Equivalent to the value found in <SingleSignOnService Location="..."/> + * in the asserting party's <IDPSSODescriptor>. + * + * @return the SingleSignOnService Location + * @since 5.4 + */ + 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 + * @since 5.4 + */ + public Saml2MessageBinding getSingleSignOnServiceBinding() { + return this.singleSignOnServiceBinding; } /** * 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 #getSingleSignOnServiceLocation()} instead */ + @Deprecated public String getWebSsoUrl() { - return webSsoUrl; + return this.singleSignOnServiceLocation; } /** * @return {@code true} if AuthNRequests from this relying party to the IDP should be signed * {@code false} if no signature is required. + * @deprecated Use {@link #getWantsAuthnRequestsSigned()} instead */ + @Deprecated public boolean isSignAuthNRequest() { - return signAuthNRequest; + return this.signAuthNRequest; } /** * @return the type of SAML 2 Binding the AuthNRequest should be sent on + * @deprecated Use {@link #getSingleSignOnServiceBinding()} instead */ + @Deprecated public Saml2MessageBinding getBinding() { - return binding; + return this.singleSignOnServiceBinding; } /** @@ -297,29 +403,84 @@ public Saml2MessageBinding getBinding() { */ public final static class Builder { private String entityId; - private String webSsoUrl; - private boolean signAuthNRequest = true; - private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT; + private String singleSignOnServiceLocation; + private Saml2MessageBinding singleSignOnServiceBinding = Saml2MessageBinding.REDIRECT; + private boolean wantsAuthnRequestsSigned = true; /** - * 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; return this; } + /** + * Set the WantsAuthnRequestsSigned setting, indicating the asserting party's preference that + * relying parties should sign the AuthnRequest before sending. + * + * @param wantsAuthnRequestsSigned the WantsAuthnRequestsSigned setting + * @return the {@link Builder} for further configuration + * @since 5.4 + */ + public Builder wantsAuthnRequestsSigned(boolean wantsAuthnRequestsSigned) { + this.wantsAuthnRequestsSigned = wantsAuthnRequestsSigned; + 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 Builder} for further configuration + * @since 5.4 + */ + 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 Builder} for further configuration + * @since 5.4 + */ + public Builder singleSignOnServiceBinding(Saml2MessageBinding singleSignOnServiceBinding) { + this.singleSignOnServiceBinding = singleSignOnServiceBinding; + 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 #singleSignOnServiceLocation} instead */ + @Deprecated public Builder webSsoUrl(String url) { - this.webSsoUrl = url; + this.singleSignOnServiceLocation = url; return this; } @@ -328,9 +489,11 @@ public Builder webSsoUrl(String url) { * * @param signAuthNRequest true if the message should be signed * @return this object + * @deprecated Use {@link #wantsAuthnRequestsSigned} instead */ + @Deprecated public Builder signAuthNRequest(boolean signAuthNRequest) { - this.signAuthNRequest = signAuthNRequest; + this.wantsAuthnRequestsSigned = signAuthNRequest; return this; } @@ -340,9 +503,11 @@ public Builder signAuthNRequest(boolean signAuthNRequest) { * * @param binding either {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT} * @return this object + * @deprecated Use {@link #singleSignOnServiceBinding} instead */ + @Deprecated public Builder binding(Saml2MessageBinding binding) { - this.binding = binding; + this.singleSignOnServiceBinding = binding; return this; } @@ -353,9 +518,9 @@ public Builder binding(Saml2MessageBinding binding) { public ProviderDetails build() { return new ProviderDetails( this.entityId, - this.webSsoUrl, - this.signAuthNRequest, - this.binding + this.wantsAuthnRequestsSigned, + this.singleSignOnServiceLocation, + this.singleSignOnServiceBinding ); } } @@ -363,10 +528,10 @@ public ProviderDetails 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,39 +549,42 @@ public Builder registrationId(String id) { } /** - * 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; } @@ -449,16 +617,56 @@ public Builder credentials(Consumer> credentials 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 #providerDetails(Consumer< ProviderDetails.Builder >)} + */ + @Deprecated + public Builder remoteIdpEntityId(String entityId) { + this.providerDetails(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 #providerDetails(Consumer< ProviderDetails.Builder >)} + */ + @Deprecated + public Builder idpWebSsoUrl(String url) { + providerDetails(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; } @@ -469,10 +677,10 @@ public Builder localEntityIdTemplate(String template) { 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/Saml2WebSsoAuthenticationFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java index 2e3d9ddb95d..8e732150683 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,7 +98,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ 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(), 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 623a04cbba6..d7b740e5131 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.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 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } Saml2AuthenticationRequestContext context = authenticationRequestContextResolver.resolve(request, relyingParty); - if (relyingParty.getProviderDetails().getBinding() == Saml2MessageBinding.REDIRECT) { + if (relyingParty.getProviderDetails().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 eae28e6d030..0f937e444dd 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 @@ private Saml2AuthenticationRequestContext createRedirectAuthenticationRequestCon 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) 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 fac6b53cc81..c4c5db23fdf 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 63ee9d55d4c..608bc62b208 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 @@ -45,6 +45,8 @@ private void compareRegistrations(RelyingPartyRegistration registration, Relying .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 +56,23 @@ private void compareRegistrations(RelyingPartyRegistration registration, Relying ); 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.getProviderDetails().getSingleSignOnServiceLocation()) + .isEqualTo(registration.getProviderDetails().getSingleSignOnServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"); assertThat(copy.getProviderDetails().getBinding()) .isEqualTo(registration.getProviderDetails().getBinding()) + .isEqualTo(copy.getProviderDetails().getSingleSignOnServiceBinding()) + .isEqualTo(registration.getProviderDetails().getSingleSignOnServiceBinding()) .isEqualTo(POST); assertThat(copy.getProviderDetails().isSignAuthNRequest()) .isEqualTo(registration.getProviderDetails().isSignAuthNRequest()) + .isEqualTo(copy.getProviderDetails().getWantsAuthnRequestsSigned()) + .isEqualTo(registration.getProviderDetails().getWantsAuthnRequestsSigned()) .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 d8c9686d41b..ccf2495c305 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 @@ 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) + .providerDetails(c -> c + .entityId(apEntityId) + .singleSignOnServiceLocation(singleSignOnServiceLocation)) .credentials(c -> c.add(signingCredential)) - .credentials(c -> c.add(idpVerificationCertificate)) - .localEntityIdTemplate(localEntityIdTemplate) - .assertionConsumerServiceUrlTemplate(acsUrlTemplate); + .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 6acdca5bd64..05d1fd9c241 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.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; @@ -55,11 +55,11 @@ RelyingPartyRegistration getSaml2AuthenticationConfiguration() throws Exception String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI; return RelyingPartyRegistration.withRegistrationId(registrationId) .providerDetails(config -> config.entityId(idpEntityId)) - .providerDetails(config -> config.webSsoUrl(webSsoEndpoint)) + .providerDetails(config -> config.singleSignOnServiceLocation(webSsoEndpoint)) .credentials(c -> c.add(signingCredential)) .credentials(c -> c.add(idpVerificationCertificate)) - .localEntityIdTemplate(localEntityIdTemplate) - .assertionConsumerServiceUrlTemplate(acsUrlTemplate) + .entityId(localEntityIdTemplate) + .assertionConsumerServiceLocation(acsUrlTemplate) .build(); }