浏览代码

Support POST binding for AuthNRequest

Has been tested with

- Keycloak
- SSOCircle
- Okta
- SimpleSAMLPhp

This PR extends (builds on previous commits and adds user configuration
options)
https://github.com/spring-projects/spring-security/pull/7758
Filip Hanik 5 年之前
父节点
当前提交
3257349045
共有 14 个文件被更改,包括 660 次插入72 次删除
  1. 2 2
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java
  2. 2 2
      config/src/test/kotlin/org/springframework/security/config/web/servlet/Saml2DslTests.kt
  3. 21 12
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
  4. 1 1
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java
  5. 201 25
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java
  6. 1 1
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java
  7. 1 1
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java
  8. 86 11
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java
  9. 62 7
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
  10. 2 2
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java
  11. 92 0
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java
  12. 143 0
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestSaml2X509Credentials.java
  13. 44 6
      saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java
  14. 2 2
      samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java

+ 2 - 2
config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java

@@ -43,8 +43,8 @@ public class TestRelyingPartyRegistrations {
 		Saml2X509Credential idpVerificationCertificate = verificationCertificate();
 		String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
 		return RelyingPartyRegistration.withRegistrationId(registrationId)
-				.remoteIdpEntityId(idpEntityId)
-				.idpWebSsoUrl(webSsoEndpoint)
+				.providerDetails(c -> c.entityId(idpEntityId))
+				.providerDetails(c -> c.webSsoUrl(webSsoEndpoint))
 				.credentials(c -> c.add(signingCredential))
 				.credentials(c -> c.add(idpVerificationCertificate))
 				.localEntityIdTemplate(localEntityIdTemplate)

+ 2 - 2
config/src/test/kotlin/org/springframework/security/config/web/servlet/Saml2DslTests.kt

@@ -85,10 +85,10 @@ class Saml2DslTests {
                     relyingPartyRegistrationRepository =
                             InMemoryRelyingPartyRegistrationRepository(
                                     RelyingPartyRegistration.withRegistrationId("samlId")
-                                            .remoteIdpEntityId("entityId")
                                             .assertionConsumerServiceUrlTemplate("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI)
                                             .credentials { c -> c.add(Saml2X509Credential(loadCert("rod.cer"), VERIFICATION)) }
-                                            .idpWebSsoUrl("ssoUrl")
+                                            .providerDetails { c -> c.webSsoUrl("ssoUrl") }
+                                            .providerDetails { c -> c.entityId("entityId") }
                                             .build()
                             )
                 }

+ 21 - 12
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java

@@ -54,7 +54,11 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
 	 */
 	@Override
 	public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
-		String xml = createAuthenticationRequest(context, context.getRelyingPartyRegistration().getSigningCredentials());
+		List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest() ?
+				context.getRelyingPartyRegistration().getSigningCredentials() :
+				emptyList();
+
+		String xml = createAuthenticationRequest(context, signingCredentials);
 		return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
 				.samlRequest(samlEncode(xml.getBytes(UTF_8)))
 				.build();
@@ -66,19 +70,24 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
 	@Override
 	public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext context) {
 		String xml = createAuthenticationRequest(context, emptyList());
-		List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials();
 		Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context);
-
 		String deflatedAndEncoded = samlEncode(samlDeflate(xml));
-		Map<String, String> signedParams = this.saml.signQueryParameters(
-				signingCredentials,
-				deflatedAndEncoded,
-				context.getRelayState()
-		);
-		result.samlRequest(signedParams.get("SAMLRequest"))
-				.relayState(signedParams.get("RelayState"))
-				.sigAlg(signedParams.get("SigAlg"))
-				.signature(signedParams.get("Signature"));
+		result.samlRequest(deflatedAndEncoded)
+				.relayState(context.getRelayState());
+
+		if (context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest()) {
+			List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials();
+			Map<String, String> signedParams = this.saml.signQueryParameters(
+					signingCredentials,
+					deflatedAndEncoded,
+					context.getRelayState()
+			);
+			result.samlRequest(signedParams.get("SAMLRequest"))
+					.relayState(signedParams.get("RelayState"))
+					.sigAlg(signedParams.get("SigAlg"))
+					.signature(signedParams.get("Signature"));
+		}
+
 		return result.build();
 	}
 

+ 1 - 1
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java

@@ -91,7 +91,7 @@ public final class Saml2AuthenticationRequestContext {
 	 * @return the Destination value
 	 */
 	public String getDestination() {
-		return this.getRelyingPartyRegistration().getIdpWebSsoUrl();
+		return this.getRelyingPartyRegistration().getProviderDetails().getWebSsoUrl();
 	}
 
 	/**

+ 201 - 25
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java

@@ -51,8 +51,8 @@ import static org.springframework.util.Assert.notNull;
  *		//IDP certificate for verification of incoming messages
  *		Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();
  *		RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(registrationId)
- * 				.remoteIdpEntityId(idpEntityId)
- * 				.idpWebSsoUrl(webSsoEndpoint)
+ * 				.providerDetails(config -> config.entityId(idpEntityId));
+ * 				.providerDetails(config -> config.webSsoUrl(url));
  * 				.credentials(c -> c.add(signingCredential))
  * 				.credentials(c -> c.add(idpVerificationCertificate))
  * 				.localEntityIdTemplate(localEntityIdTemplate)
@@ -64,37 +64,41 @@ import static org.springframework.util.Assert.notNull;
 public class RelyingPartyRegistration {
 
 	private final String registrationId;
-	private final String remoteIdpEntityId;
 	private final String assertionConsumerServiceUrlTemplate;
-	private final String idpWebSsoUrl;
 	private final List<Saml2X509Credential> credentials;
 	private final String localEntityIdTemplate;
-
-	private RelyingPartyRegistration(String idpEntityId, String registrationId, String assertionConsumerServiceUrlTemplate,
-			String idpWebSsoUri, List<Saml2X509Credential> credentials, String localEntityIdTemplate) {
-		hasText(idpEntityId, "idpEntityId cannot be empty");
+	private final ProviderDetails providerDetails;
+
+	private RelyingPartyRegistration(
+			String registrationId,
+			String assertionConsumerServiceUrlTemplate,
+			ProviderDetails providerDetails,
+			List<Saml2X509Credential> 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(idpWebSsoUri, "idpWebSsoUri cannot be empty");
+		notNull(providerDetails, "providerDetails cannot be null");
+		hasText(providerDetails.webSsoUrl, "providerDetails.webSsoUrl cannot be empty");
 		for (Saml2X509Credential c : credentials) {
 			notNull(c, "credentials cannot contain null elements");
 		}
 		this.registrationId = registrationId;
-		this.remoteIdpEntityId = idpEntityId;
 		this.assertionConsumerServiceUrlTemplate = assertionConsumerServiceUrlTemplate;
 		this.credentials = unmodifiableList(new LinkedList<>(credentials));
-		this.idpWebSsoUrl = idpWebSsoUri;
+		this.providerDetails = providerDetails;
 		this.localEntityIdTemplate = localEntityIdTemplate;
 	}
 
 	/**
 	 * 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
 	public String getRemoteIdpEntityId() {
-		return this.remoteIdpEntityId;
+		return this.providerDetails.getEntityId();
 	}
 
 	/**
@@ -119,9 +123,20 @@ public class RelyingPartyRegistration {
 	 * 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
 	public String getIdpWebSsoUrl() {
-		return this.idpWebSsoUrl;
+		return this.getProviderDetails().webSsoUrl;
+	}
+
+	/**
+	 * Returns specific configuration around the Identity Provider SSO endpoint
+	 * @return the IDP SSO endpoint configuration
+	 * @since 5.3
+	 */
+	public ProviderDetails getProviderDetails() {
+		return this.providerDetails;
 	}
 
 	/**
@@ -200,13 +215,158 @@ public class RelyingPartyRegistration {
 		return new Builder(registrationId);
 	}
 
-	public static class Builder {
+	/**
+	 * Creates a {@code RelyingPartyRegistration} {@link Builder} based on an existing object
+	 * @param registration the {@code RelyingPartyRegistration}
+	 * @return {@code Builder} to create a {@code RelyingPartyRegistration} object
+	 */
+	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())
+				;
+	}
+
+	/**
+	 * Configuration for IDP SSO endpoint configuration
+	 * @since 5.3
+	 */
+	public final static class ProviderDetails {
+		private final String entityId;
+		private final String webSsoUrl;
+		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");
+			this.entityId = entityId;
+			this.webSsoUrl = webSsoUrl;
+			this.signAuthNRequest = signAuthNRequest;
+			this.binding = binding;
+		}
+
+		/**
+		 * Returns the entity ID of the Identity Provider
+		 * @return the entity ID of the IDP
+		 */
+		public String getEntityId() {
+			return entityId;
+		}
+
+		/**
+		 * 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
+		 */
+		public String getWebSsoUrl() {
+			return webSsoUrl;
+		}
+
+		/**
+		 * @return {@code true} if AuthNRequests from this relying party to the IDP should be signed
+		 * {@code false} if no signature is required.
+		 */
+		public boolean isSignAuthNRequest() {
+			return signAuthNRequest;
+		}
+
+		/**
+		 * @return the type of SAML 2 Binding the AuthNRequest should be sent on
+		 */
+		public Saml2MessageBinding getBinding() {
+			return binding;
+		}
+
+		/**
+		 * Builder for IDP SSO endpoint configuration
+		 * @since 5.3
+		 */
+		public final static class Builder {
+			private String entityId;
+			private String webSsoUrl;
+			private boolean signAuthNRequest = true;
+			private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT;
+
+			/**
+			 * Sets the {@code EntityID} for the remote asserting party, the Identity Provider.
+			 *
+			 * @param entityId - the EntityID of the IDP. May be a URL.
+			 * @return this object
+			 */
+			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
+			 */
+			public Builder webSsoUrl(String url) {
+				this.webSsoUrl = url;
+				return this;
+			}
+
+			/**
+			 * Set to true if the AuthNRequest message should be signed
+			 *
+			 * @param signAuthNRequest true if the message should be signed
+			 * @return this object
+			 */
+			public Builder signAuthNRequest(boolean signAuthNRequest) {
+				this.signAuthNRequest = signAuthNRequest;
+				return this;
+			}
+
+
+			/**
+			 * Sets the message binding to be used when sending an AuthNRequest message
+			 *
+			 * @param binding either {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT}
+			 * @return this object
+			 */
+			public Builder binding(Saml2MessageBinding binding) {
+				this.binding = binding;
+				return this;
+			}
+
+			/**
+			 * Creates an immutable ProviderDetails object representing the configuration for an Identity Provider, IDP
+			 * @return immutable ProviderDetails object
+			 */
+			public ProviderDetails build() {
+				return new ProviderDetails(
+						this.entityId,
+						this.webSsoUrl,
+						this.signAuthNRequest,
+						this.binding
+				);
+			}
+		}
+	}
+
+	public final static class Builder {
 		private String registrationId;
-		private String remoteIdpEntityId;
-		private String idpWebSsoUrl;
 		private String assertionConsumerServiceUrlTemplate;
 		private List<Saml2X509Credential> credentials = new LinkedList<>();
 		private String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
+		private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
 
 		private Builder(String registrationId) {
 			this.registrationId = registrationId;
@@ -227,9 +387,11 @@ 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.remoteIdpEntityId = entityId;
+			this.providerDetails(idp -> idp.entityId(entityId));
 			return this;
 		}
 
@@ -250,9 +412,21 @@ public class RelyingPartyRegistration {
 		 * 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) {
-			this.idpWebSsoUrl = url;
+			providerDetails(config -> config.webSsoUrl(url));
+			return this;
+		}
+
+		/**
+		 * Configures the IDP SSO endpoint
+		 * @param providerDetails a consumer that configures the IDP SSO endpoint
+		 * @return this object
+		 */
+		public Builder providerDetails(Consumer<ProviderDetails.Builder> providerDetails) {
+			providerDetails.accept(this.providerDetails);
 			return this;
 		}
 
@@ -288,17 +462,19 @@ public class RelyingPartyRegistration {
 			return this;
 		}
 
+		/**
+		 * Constructs a RelyingPartyRegistration object based on the builder configurations
+		 * @return a RelyingPartyRegistration instance
+		 */
 		public RelyingPartyRegistration build() {
 			return new RelyingPartyRegistration(
-					remoteIdpEntityId,
-					registrationId,
-					assertionConsumerServiceUrlTemplate,
-					idpWebSsoUrl,
-					credentials,
-					localEntityIdTemplate
+					this.registrationId,
+					this.assertionConsumerServiceUrlTemplate,
+					this.providerDetails.build(),
+					this.credentials,
+					this.localEntityIdTemplate
 			);
 		}
 	}
 
-
 }

+ 1 - 1
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java

@@ -39,7 +39,7 @@ final class Saml2ServletUtils {
 		return resolveUrlTemplate(
 				rp.getLocalEntityIdTemplate(),
 				getApplicationUri(request),
-				rp.getRemoteIdpEntityId(),
+				rp.getProviderDetails().getEntityId(),
 				rp.getRegistrationId()
 		);
 	}

+ 1 - 1
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java

@@ -101,7 +101,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
 		final Saml2AuthenticationToken authentication = new Saml2AuthenticationToken(
 				responseXml,
 				request.getRequestURL().toString(),
-				rp.getRemoteIdpEntityId(),
+				rp.getProviderDetails().getEntityId(),
 				localSpEntityId,
 				rp.getCredentials()
 		);

+ 86 - 11
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java

@@ -16,17 +16,21 @@
 
 package org.springframework.security.saml2.provider.service.servlet.filter;
 
+import org.springframework.http.MediaType;
 import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory;
+import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
 import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher.MatchResult;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.HtmlUtils;
 import org.springframework.web.util.UriComponentsBuilder;
 import org.springframework.web.util.UriUtils;
 
@@ -74,23 +78,42 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
 		}
 
 		String registrationId = matcher.getVariables().get("registrationId");
-		sendRedirect(request, response, registrationId);
+		RelyingPartyRegistration relyingParty = this.relyingPartyRegistrationRepository.findByRegistrationId(registrationId);
+		if (relyingParty == null) {
+			response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+			return;
+		}
+		if (this.logger.isDebugEnabled()) {
+			this.logger.debug(format("Creating SAML2 SP Authentication Request for IDP[%s]", relyingParty.getRegistrationId()));
+		}
+		Saml2AuthenticationRequestContext authnRequestCtx = createRedirectAuthenticationRequestContext(relyingParty, request);
+		if (relyingParty.getProviderDetails().getBinding() == Saml2MessageBinding.REDIRECT) {
+			sendRedirect(response, authnRequestCtx);
+		}
+		else {
+			sendPost(response, authnRequestCtx);
+		}
 	}
 
-	private void sendRedirect(HttpServletRequest request, HttpServletResponse response, String registrationId)
+	private void sendRedirect(HttpServletResponse response, Saml2AuthenticationRequestContext authnRequestCtx)
 			throws IOException {
-		if (this.logger.isDebugEnabled()) {
-			this.logger.debug(format("Creating SAML2 SP Authentication Request for IDP[%s]", registrationId));
-		}
-		RelyingPartyRegistration relyingParty = this.relyingPartyRegistrationRepository.findByRegistrationId(registrationId);
-		String redirectUrl = createSamlRequestRedirectUrl(request, relyingParty);
+		String redirectUrl = createSamlRequestRedirectUrl(authnRequestCtx);
 		response.sendRedirect(redirectUrl);
 	}
 
-	private String createSamlRequestRedirectUrl(HttpServletRequest request, RelyingPartyRegistration relyingParty) {
-		Saml2AuthenticationRequestContext authnRequest = createRedirectAuthenticationRequestContext(relyingParty, request);
+	private void sendPost(HttpServletResponse response, Saml2AuthenticationRequestContext authnRequestCtx)
+			throws IOException {
+		Saml2PostAuthenticationRequest authNData =
+				this.authenticationRequestFactory.createPostAuthenticationRequest(authnRequestCtx);
+		String html = createSamlPostRequestFormData(authNData);
+		response.setContentType(MediaType.TEXT_HTML_VALUE);
+		response.getWriter().write(html);
+	}
+
+	private String createSamlRequestRedirectUrl(Saml2AuthenticationRequestContext authnRequestCtx) {
+
 		Saml2RedirectAuthenticationRequest authNData =
-				this.authenticationRequestFactory.createRedirectAuthenticationRequest(authnRequest);
+				this.authenticationRequestFactory.createRedirectAuthenticationRequest(authnRequestCtx);
 		UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authNData.getAuthenticationRequestUri());
 		addParameter("SAMLRequest", authNData.getSamlRequest(), uriBuilder);
 		addParameter("RelayState", authNData.getRelayState(), uriBuilder);
@@ -123,7 +146,7 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
 						Saml2ServletUtils.resolveUrlTemplate(
 								relyingParty.getAssertionConsumerServiceUrlTemplate(),
 								Saml2ServletUtils.getApplicationUri(request),
-								relyingParty.getRemoteIdpEntityId(),
+								relyingParty.getProviderDetails().getEntityId(),
 								relyingParty.getRegistrationId()
 						)
 				)
@@ -131,4 +154,56 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
 				.build()
 				;
 	}
+
+	private String htmlEscape(String value) {
+		if (hasText(value)) {
+			return HtmlUtils.htmlEscape(value);
+		}
+		return value;
+	}
+
+	private String createSamlPostRequestFormData(Saml2PostAuthenticationRequest request) {
+		String destination = request.getAuthenticationRequestUri();
+		String relayState = htmlEscape(request.getRelayState());
+		String samlRequest = htmlEscape(request.getSamlRequest());
+		StringBuilder postHtml = new StringBuilder()
+				.append("<!DOCTYPE html>\n")
+				.append("<html>\n")
+				.append("    <head>\n")
+				.append("        <meta charset=\"utf-8\" />\n")
+				.append("    </head>\n")
+				.append("    <body onload=\"document.forms[0].submit()\">\n")
+				.append("        <noscript>\n")
+				.append("            <p>\n")
+				.append("                <strong>Note:</strong> Since your browser does not support JavaScript,\n")
+				.append("                you must press the Continue button once to proceed.\n")
+				.append("            </p>\n")
+				.append("        </noscript>\n")
+				.append("        \n")
+				.append("        <form action=\"").append(destination).append("\" method=\"post\">\n")
+				.append("            <div>\n")
+				.append("                <input type=\"hidden\" name=\"SAMLRequest\" value=\"")
+				.append(samlRequest)
+				.append("\"/>\n")
+				;
+		if (hasText(relayState)) {
+			postHtml
+					.append("                <input type=\"hidden\" name=\"RelayState\" value=\"")
+					.append(relayState)
+					.append("\"/>\n");
+		}
+		postHtml
+				.append("            </div>\n")
+				.append("            <noscript>\n")
+				.append("                <div>\n")
+				.append("                    <input type=\"submit\" value=\"Continue\"/>\n")
+				.append("                </div>\n")
+				.append("            </noscript>\n")
+				.append("        </form>\n")
+				.append("        \n")
+				.append("    </body>\n")
+				.append("</html>")
+		;
+		return postHtml.toString();
+	}
 }

+ 62 - 7
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java

@@ -26,12 +26,12 @@ import org.opensaml.saml.saml2.core.AuthnRequest;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
 import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
 
-import java.nio.charset.StandardCharsets;
-
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode;
 import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials;
+import static org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.withRelyingPartyRegistration;
 import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
 import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT;
 
@@ -46,19 +46,20 @@ public class OpenSamlAuthenticationRequestFactoryTests {
 
 	@Rule
 	public ExpectedException exception = ExpectedException.none();
+	private RelyingPartyRegistration relyingPartyRegistration;
 
 	@Before
 	public void setUp() {
-		RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id")
+		relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("id")
 				.assertionConsumerServiceUrlTemplate("template")
-				.idpWebSsoUrl("https://destination/sso")
-				.remoteIdpEntityId("remote-entity-id")
+				.providerDetails(c -> c.webSsoUrl("https://destination/sso"))
+				.providerDetails(c -> c.entityId("remote-entity-id"))
 				.localEntityIdTemplate("local-entity-id")
 				.credentials(c -> c.addAll(relyingPartyCredentials()))
 				.build();
 		contextBuilder = Saml2AuthenticationRequestContext.builder()
 				.issuer("https://issuer")
-				.relyingPartyRegistration(registration)
+				.relyingPartyRegistration(relyingPartyRegistration)
 				.assertionConsumerServiceUrl("https://issuer/sso");
 		context = contextBuilder.build();
 		factory = new OpenSamlAuthenticationRequestFactory();
@@ -84,6 +85,60 @@ public class OpenSamlAuthenticationRequestFactoryTests {
 		assertThat(result.getBinding()).isEqualTo(REDIRECT);
 	}
 
+	@Test
+	public void createRedirectAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() {
+
+		context = contextBuilder
+				.relayState("Relay State Value")
+				.relyingPartyRegistration(
+						withRelyingPartyRegistration(relyingPartyRegistration)
+						.providerDetails(c -> c.signAuthNRequest(false))
+						.build()
+				)
+				.build();
+		Saml2RedirectAuthenticationRequest result = factory.createRedirectAuthenticationRequest(context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getSigAlg()).isNull();
+		assertThat(result.getSignature()).isNull();
+		assertThat(result.getBinding()).isEqualTo(REDIRECT);
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() {
+		context = contextBuilder
+				.relayState("Relay State Value")
+				.relyingPartyRegistration(
+						withRelyingPartyRegistration(relyingPartyRegistration)
+								.providerDetails(c -> c.signAuthNRequest(false))
+								.build()
+				)
+				.build();
+		Saml2PostAuthenticationRequest result = factory.createPostAuthenticationRequest(context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getBinding()).isEqualTo(POST);
+		assertThat(new String(samlDecode(result.getSamlRequest()), UTF_8))
+				.doesNotContain("ds:Signature");
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenSignRequestThenSignatureIsPresent() {
+		context = contextBuilder
+				.relayState("Relay State Value")
+				.relyingPartyRegistration(
+						withRelyingPartyRegistration(relyingPartyRegistration)
+								.build()
+				)
+				.build();
+		Saml2PostAuthenticationRequest result = factory.createPostAuthenticationRequest(context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getBinding()).isEqualTo(POST);
+		assertThat(new String(samlDecode(result.getSamlRequest()), UTF_8))
+				.contains("ds:Signature");
+	}
+
 	@Test
 	public void createAuthenticationRequestWhenDefaultThenReturnsPostBinding() {
 		AuthnRequest authn = getAuthNRequest(POST);
@@ -114,7 +169,7 @@ public class OpenSamlAuthenticationRequestFactoryTests {
 			samlRequest = Saml2Utils.samlInflate(samlDecode(samlRequest));
 		}
 		else {
-			samlRequest = new String(samlDecode(samlRequest), StandardCharsets.UTF_8);
+			samlRequest = new String(samlDecode(samlRequest), UTF_8);
 		}
 		return (AuthnRequest) OpenSamlImplementation.getInstance().resolve(samlRequest);
 	}

+ 2 - 2
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java

@@ -33,8 +33,8 @@ public class Saml2AuthenticationRequestFactoryTests {
 
 	private RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id")
 			.assertionConsumerServiceUrlTemplate("template")
-			.idpWebSsoUrl("https://example.com/destination")
-			.remoteIdpEntityId("remote-entity-id")
+			.providerDetails(c -> c.webSsoUrl("https://example.com/destination"))
+			.providerDetails(c -> c.entityId("remote-entity-id"))
 			.localEntityIdTemplate("local-entity-id")
 			.credentials(c -> c.addAll(relyingPartyCredentials()))
 			.build();

+ 92 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.saml2.provider.service.registration;
+
+import org.junit.Test;
+import org.springframework.security.saml2.credentials.Saml2X509Credential;
+import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RelyingPartyRegistrationTests {
+
+	@Test
+	public void withRelyingPartyRegistrationWorks() {
+		RelyingPartyRegistration registration = relyingPartyRegistration();
+		RelyingPartyRegistration copy = RelyingPartyRegistration.withRelyingPartyRegistration(registration).build();
+		compareRegistrations(registration, copy);
+	}
+
+	private void compareRegistrations(RelyingPartyRegistration registration, RelyingPartyRegistration copy) {
+		assertThat(copy.getRegistrationId())
+				.isEqualTo(registration.getRegistrationId())
+				.isEqualTo("simplesamlphp");
+		assertThat(copy.getProviderDetails().getEntityId())
+				.isEqualTo(registration.getProviderDetails().getEntityId())
+				.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
+		assertThat(copy.getAssertionConsumerServiceUrlTemplate())
+				.isEqualTo(registration.getAssertionConsumerServiceUrlTemplate())
+				.isEqualTo("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
+		assertThat(copy.getCredentials())
+				.containsAll(registration.getCredentials())
+				.containsExactly(
+						registration.getCredentials().get(0),
+						registration.getCredentials().get(1)
+				);
+		assertThat(copy.getLocalEntityIdTemplate())
+				.isEqualTo(registration.getLocalEntityIdTemplate())
+				.isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}");
+		assertThat(copy.getProviderDetails().getWebSsoUrl())
+				.isEqualTo(registration.getProviderDetails().getWebSsoUrl())
+				.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php");
+		assertThat(copy.getProviderDetails().getBinding())
+				.isEqualTo(registration.getProviderDetails().getBinding())
+				.isEqualTo(Saml2MessageBinding.POST);
+		assertThat(copy.getProviderDetails().isSignAuthNRequest())
+				.isEqualTo(registration.getProviderDetails().isSignAuthNRequest())
+				.isFalse();
+	}
+
+
+	private RelyingPartyRegistration 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)
+		Saml2X509Credential signingCredential = TestSaml2X509Credentials.relyingPartyCredentials().get(0);
+		//IDP certificate for verification of incoming messages
+		Saml2X509Credential idpVerificationCertificate = TestSaml2X509Credentials.relyingPartyCredentials().get(1);
+		String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
+		return RelyingPartyRegistration.withRegistrationId(registrationId)
+				.providerDetails(c -> {
+					c.webSsoUrl(webSsoEndpoint);
+					c.binding(Saml2MessageBinding.POST);
+					c.signAuthNRequest(false);
+					c.entityId(idpEntityId);
+				})
+				.credentials(c -> c.add(signingCredential))
+				.credentials(c -> c.add(idpVerificationCertificate))
+				.localEntityIdTemplate(localEntityIdTemplate)
+				.assertionConsumerServiceUrlTemplate(acsUrlTemplate)
+				.build();
+	}
+}

+ 143 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestSaml2X509Credentials.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.saml2.provider.service.registration;
+
+import org.opensaml.security.crypto.KeySupport;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.credentials.Saml2X509Credential;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION;
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION;
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING;
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION;
+
+final class TestSaml2X509Credentials {
+	static List<Saml2X509Credential> relyingPartyCredentials() {
+		return Arrays.asList(
+				new Saml2X509Credential(
+						spPrivateKey(),
+						spCertificate(),
+						SIGNING,
+						DECRYPTION
+				),
+				new Saml2X509Credential(
+						idpCertificate(),
+						ENCRYPTION,
+						VERIFICATION
+				)
+		);
+	}
+
+	private static X509Certificate certificate(String cert) {
+		ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes());
+		try {
+			return (X509Certificate) CertificateFactory
+					.getInstance("X.509")
+					.generateCertificate(certBytes);
+		}
+		catch (CertificateException e) {
+			throw new Saml2Exception(e);
+		}
+	}
+
+	private static PrivateKey privateKey(String key) {
+		try {
+			return KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]);
+		}
+		catch (KeyException e) {
+			throw new Saml2Exception(e);
+		}
+	}
+
+	private static X509Certificate idpCertificate()  {
+		return certificate("-----BEGIN CERTIFICATE-----\n"
+				+ "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n"
+				+ "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n"
+				+ "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n"
+				+ "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n"
+				+ "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n"
+				+ "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n"
+				+ "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n"
+				+ "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n"
+				+ "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n"
+				+ "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n"
+				+ "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n"
+				+ "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n"
+				+ "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n"
+				+ "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n"
+				+ "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n"
+				+ "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n"
+				+ "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n"
+				+ "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n"
+				+ "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n"
+				+ "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n"
+				+ "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n"
+				+ "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n"
+				+ "-----END CERTIFICATE-----\n");
+	}
+
+
+	private static X509Certificate spCertificate() {
+
+		return certificate("-----BEGIN CERTIFICATE-----\n" +
+				"MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC\n" +
+				"VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG\n" +
+				"A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD\n" +
+				"DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1\n" +
+				"MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES\n" +
+				"MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN\n" +
+				"TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s\n" +
+				"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos\n" +
+				"vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM\n" +
+				"+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG\n" +
+				"y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi\n" +
+				"XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+\n" +
+				"qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD\n" +
+				"RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" +
+				"-----END CERTIFICATE-----");
+	}
+
+	private static PrivateKey spPrivateKey() {
+		return privateKey("-----BEGIN PRIVATE KEY-----\n" +
+				"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" +
+				"VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" +
+				"cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" +
+				"Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" +
+				"x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" +
+				"wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" +
+				"vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" +
+				"8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" +
+				"oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" +
+				"EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" +
+				"KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" +
+				"YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" +
+				"9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" +
+				"INrtuLp4YHbgk1mi\n" +
+				"-----END PRIVATE KEY-----");
+	}
+
+}

+ 44 - 6
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java

@@ -16,10 +16,6 @@
 
 package org.springframework.security.saml2.provider.service.servlet.filter;
 
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import javax.servlet.ServletException;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.mock.web.MockFilterChain;
@@ -27,11 +23,17 @@ import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
+import org.springframework.web.util.HtmlUtils;
 import org.springframework.web.util.UriUtils;
 
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
 import static org.springframework.security.saml2.provider.service.servlet.filter.TestSaml2SigningCredentials.signingCredential;
 
 public class Saml2WebSsoAuthenticationRequestFilterTests {
@@ -55,8 +57,8 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
 
 		rpBuilder = RelyingPartyRegistration
 				.withRegistrationId("registration-id")
-				.remoteIdpEntityId("idp-entity-id")
-				.idpWebSsoUrl(IDP_SSO_URL)
+				.providerDetails(c -> c.entityId("idp-entity-id"))
+				.providerDetails(c -> c.webSsoUrl(IDP_SSO_URL))
 				.assertionConsumerServiceUrlTemplate("template")
 				.credentials(c -> c.add(signingCredential()));
 	}
@@ -109,4 +111,40 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
 				.startsWith(IDP_SSO_URL);
 	}
 
+	@Test
+	public void doFilterWhenSignatureIsDisabledThenSignatureParametersAreNotInTheRedirectURL() throws Exception {
+		when(repository.findByRegistrationId("registration-id")).thenReturn(
+				rpBuilder
+						.providerDetails(c -> c.signAuthNRequest(false))
+						.build()
+		);
+		final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
+		final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
+		request.setParameter("RelayState", relayStateValue);
+		filter.doFilterInternal(request, response, filterChain);
+		assertThat(response.getHeader("Location"))
+				.contains("RelayState="+relayStateEncoded)
+				.doesNotContain("SigAlg=")
+				.doesNotContain("Signature=")
+				.startsWith(IDP_SSO_URL);
+	}
+
+	@Test
+	public void doFilterWhenPostFormDataIsPresent() throws Exception {
+		when(repository.findByRegistrationId("registration-id")).thenReturn(
+				rpBuilder
+						.providerDetails(c -> c.binding(POST))
+						.build()
+		);
+		final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param&javascript{alert('1');}";
+		final String relayStateEncoded = HtmlUtils.htmlEscape(relayStateValue);
+		request.setParameter("RelayState", relayStateValue);
+		filter.doFilterInternal(request, response, filterChain);
+		assertThat(response.getHeader("Location")).isNull();
+		assertThat(response.getContentAsString())
+				.contains("<form action=\"https://sso-url.example.com/IDP/SSO\" method=\"post\">")
+				.contains("<input type=\"hidden\" name=\"SAMLRequest\"")
+				.contains("value=\""+relayStateEncoded+"\"");
+	}
+
 }

+ 2 - 2
samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java

@@ -54,8 +54,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();
 		String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
 		return RelyingPartyRegistration.withRegistrationId(registrationId)
-				.remoteIdpEntityId(idpEntityId)
-				.idpWebSsoUrl(webSsoEndpoint)
+				.providerDetails(config -> config.entityId(idpEntityId))
+				.providerDetails(config -> config.webSsoUrl(webSsoEndpoint))
 				.credentials(c -> c.add(signingCredential))
 				.credentials(c -> c.add(idpVerificationCertificate))
 				.localEntityIdTemplate(localEntityIdTemplate)