Просмотр исходного кода

Add OpenSAML 4 Support

Closes gh-9095
Josh Cummings 4 лет назад
Родитель
Сommit
d0d0a8d958
85 измененных файлов с 3137 добавлено и 614 удалено
  1. 8 2
      config/spring-security-config.gradle
  2. 16 4
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java
  3. 7 7
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
  4. 53 0
      saml2/saml2-service-provider/core/saml2-service-provider-core.gradle
  5. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/Saml2Exception.java
  6. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java
  7. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2Error.java
  8. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java
  9. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java
  10. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java
  11. 80 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/SpringSecurityAuthnRequestBuilder.java
  12. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java
  13. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java
  14. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java
  15. 113 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlDecryptionUtils.java
  16. 173 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlSigningUtils.java
  17. 217 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlVerificationUtils.java
  18. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java
  19. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java
  20. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java
  21. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java
  22. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java
  23. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java
  24. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java
  25. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java
  26. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java
  27. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java
  28. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java
  29. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java
  30. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java
  31. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java
  32. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java
  33. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java
  34. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java
  35. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java
  36. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java
  37. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java
  38. 1 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
  39. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java
  40. 18 2
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java
  41. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java
  42. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java
  43. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java
  44. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java
  45. 0 0
      saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java
  46. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java
  47. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java
  48. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java
  49. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java
  50. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java
  51. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java
  52. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java
  53. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java
  54. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java
  55. 1 20
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java
  56. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java
  57. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java
  58. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java
  59. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java
  60. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java
  61. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
  62. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java
  63. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java
  64. 73 66
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java
  65. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java
  66. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java
  67. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java
  68. 0 0
      saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java
  69. 0 0
      saml2/saml2-service-provider/core/src/test/resources/logback-test.xml
  70. 0 0
      saml2/saml2-service-provider/core/src/test/resources/saml2-response-sso-circle.encoded
  71. 0 0
      saml2/saml2-service-provider/core/src/test/resources/test-metadata.xml
  72. 53 0
      saml2/saml2-service-provider/opensaml3/saml2-service-provider-opensaml3.gradle
  73. 21 141
      saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
  74. 203 0
      saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
  75. 94 47
      saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
  76. 9 4
      saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java
  77. 58 0
      saml2/saml2-service-provider/opensaml4/saml2-service-provider-opensaml4.gradle
  78. 770 0
      saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java
  79. 180 0
      saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java
  80. 661 0
      saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java
  81. 274 0
      saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java
  82. 42 8
      saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle
  83. 0 311
      saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
  84. 7 1
      samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle
  85. 5 1
      samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2login.gradle

+ 8 - 2
config/spring-security-config.gradle

@@ -4,6 +4,10 @@ apply plugin: 'io.spring.convention.spring-module'
 apply plugin: 'trang'
 apply plugin: 'kotlin'
 
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
 dependencies {
 	// NB: Don't add other compile time dependencies to the config module as this breaks tooling
 	compile project(':spring-security-core')
@@ -14,7 +18,8 @@ dependencies {
 
 	optional project(':spring-security-ldap')
 	optional project(':spring-security-messaging')
-	optional project(':spring-security-saml2-service-provider')
+	optional project(':saml2-service-provider-opensaml3')
+	optional project(':saml2-service-provider-opensaml4')
 	optional project(':spring-security-oauth2-client')
 	optional project(':spring-security-oauth2-jose')
 	optional project(':spring-security-oauth2-resource-server')
@@ -42,7 +47,8 @@ dependencies {
 	testCompile project(path : ':spring-security-ldap', configuration : 'tests')
 	testCompile project(path : ':spring-security-oauth2-client', configuration : 'tests')
 	testCompile project(path : ':spring-security-oauth2-resource-server', configuration : 'tests')
-	testCompile project(path : ':spring-security-saml2-service-provider', configuration : 'tests')
+	testCompile project(path : ':saml2-service-provider-core', configuration : 'tests')
+	testCompile project(path : ':saml2-service-provider-opensaml4', configuration : 'tests')
 	testCompile project(path : ':spring-security-web', configuration : 'tests')
 	testCompile apachedsDependencies
 	testCompile powerMock2Dependencies

+ 16 - 4
config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

@@ -21,15 +21,20 @@ import java.util.Map;
 
 import javax.servlet.Filter;
 
+import org.opensaml.core.Version;
+
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
+import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationRequestFactory;
 import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
 import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory;
@@ -190,7 +195,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	 * <li>The {@code loginProcessingUrl} is set</li>
 	 * <li>A custom login page is configured, <b>or</b></li>
 	 * <li>A default login page with all SAML 2.0 Identity Providers is configured</li>
-	 * <li>An {@link OpenSamlAuthenticationProvider} is configured</li>
+	 * <li>An {@link AuthenticationProvider} is configured</li>
 	 * </ul>
 	 */
 	@Override
@@ -256,8 +261,12 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	}
 
 	private void registerDefaultAuthenticationProvider(B http) {
-		OpenSamlAuthenticationProvider provider = postProcess(new OpenSamlAuthenticationProvider());
-		http.authenticationProvider(provider);
+		if (Version.getVersion().startsWith("4")) {
+			http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
+		}
+		else {
+			http.authenticationProvider(postProcess(new OpenSamlAuthenticationProvider()));
+		}
 	}
 
 	private void registerDefaultCsrfOverride(B http) {
@@ -337,7 +346,10 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		private Saml2AuthenticationRequestFactory getResolver(B http) {
 			Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
 			if (resolver == null) {
-				resolver = new OpenSamlAuthenticationRequestFactory();
+				if (Version.getVersion().startsWith("4")) {
+					return new OpenSaml4AuthenticationRequestFactory();
+				}
+				return new OpenSamlAuthenticationRequestFactory();
 			}
 			return resolver;
 		}

+ 7 - 7
config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java

@@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.web.configurers.saml2;
 import java.io.IOException;
 import java.net.URLDecoder;
 import java.time.Duration;
+import java.time.Instant;
 import java.util.Base64;
 import java.util.Collection;
 import java.util.Collections;
@@ -61,8 +62,9 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
 import org.springframework.security.saml2.core.Saml2ErrorCodes;
 import org.springframework.security.saml2.core.Saml2Utils;
 import org.springframework.security.saml2.core.TestSaml2X509Credentials;
+import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
+import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationRequestFactory;
 import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
-import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory;
 import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
@@ -235,11 +237,8 @@ public class Saml2LoginConfigurerTests {
 				"authenticationManager");
 		ProviderManager pm = (ProviderManager) manager;
 		AuthenticationProvider provider = pm.getProviders().stream()
-				.filter((p) -> p instanceof OpenSamlAuthenticationProvider).findFirst().get();
-		Assert.assertSame(AUTHORITIES_EXTRACTOR, ReflectionTestUtils.getField(provider, "authoritiesExtractor"));
-		Assert.assertSame(AUTHORITIES_MAPPER, ReflectionTestUtils.getField(provider, "authoritiesMapper"));
-		Assert.assertSame(RESPONSE_TIME_VALIDATION_SKEW,
-				ReflectionTestUtils.getField(provider, "responseTimeValidationSkew"));
+				.filter((p) -> p instanceof OpenSaml4AuthenticationProvider).findFirst().get();
+		assertThat(provider).isNotNull();
 	}
 
 	private Saml2WebSsoAuthenticationFilter getSaml2SsoFilter(FilterChainProxy chain) {
@@ -370,9 +369,10 @@ public class Saml2LoginConfigurerTests {
 
 		@Bean
 		Saml2AuthenticationRequestFactory authenticationRequestFactory() {
-			OpenSamlAuthenticationRequestFactory authenticationRequestFactory = new OpenSamlAuthenticationRequestFactory();
+			OpenSaml4AuthenticationRequestFactory authenticationRequestFactory = new OpenSaml4AuthenticationRequestFactory();
 			authenticationRequestFactory.setAuthenticationRequestContextConverter((context) -> {
 				AuthnRequest authnRequest = TestOpenSamlObjects.authnRequest();
+				authnRequest.setIssueInstant(Instant.now());
 				authnRequest.setForceAuthn(true);
 				return authnRequest;
 			});

+ 53 - 0
saml2/saml2-service-provider/core/saml2-service-provider-core.gradle

@@ -0,0 +1,53 @@
+buildscript {
+	repositories {
+		maven { url 'https://repo.spring.io/plugins-release' }
+	}
+	dependencies {
+		classpath 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
+	}
+}
+
+plugins {
+	id 'java-library'
+	id 'io.spring.convention.repository'
+	id 'io.spring.convention.springdependencymangement'
+	id 'io.spring.convention.dependency-set'
+	id 'io.spring.convention.checkstyle'
+	id 'io.spring.convention.tests-configuration'
+	id 'io.spring.convention.integration-test'
+	id 'propdeps'
+}
+
+configurations {
+	classesOnlyElements {
+		canBeConsumed = true
+		canBeResolved = false
+	}
+}
+
+artifacts {
+	classesOnlyElements(compileJava.destinationDir)
+}
+
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
+dependencies {
+	constraints {
+		management("org.opensaml:opensaml-core:3.+")
+		management("org.opensaml:opensaml-saml-api:3.+")
+		management("org.opensaml:opensaml-saml-impl:3.+")
+	}
+
+	compile project(':spring-security-core')
+	compile project(':spring-security-web')
+
+	provided("org.opensaml:opensaml-core")
+	provided("org.opensaml:opensaml-saml-api")
+	provided("org.opensaml:opensaml-saml-impl")
+
+	provided 'javax.servlet:javax.servlet-api'
+
+	testCompile 'com.squareup.okhttp3:mockwebserver'
+}

+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/Saml2Exception.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2Error.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java


+ 80 - 0
saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/SpringSecurityAuthnRequestBuilder.java

@@ -0,0 +1,80 @@
+/*
+ * 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.core;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.opensaml.core.xml.XMLObjectBuilder;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
+
+/**
+ * A {@link AuthnRequestBuilder} that gives each {@link AuthnRequest} some reasonable
+ * defaults.
+ *
+ * @author Josh Cummings
+ * @since 5.5
+ */
+public final class SpringSecurityAuthnRequestBuilder extends AuthnRequestBuilder {
+
+	private final XMLObjectBuilder<AuthnRequest> builder;
+
+	private Clock clock = Clock.systemUTC();
+
+	SpringSecurityAuthnRequestBuilder(XMLObjectBuilder<AuthnRequest> builder) {
+		this.builder = builder;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public AuthnRequest buildObject(final String namespaceURI, final String localName, final String namespacePrefix) {
+		AuthnRequest authnRequest = this.builder.buildObject(namespaceURI, localName, namespacePrefix);
+		setDefaults(authnRequest);
+		return authnRequest;
+	}
+
+	/**
+	 * Use this {@link Clock} with {@link Instant#now()} for generating timestamps
+	 * @param clock
+	 */
+	public void setClock(Clock clock) {
+		this.clock = clock;
+	}
+
+	private void setDefaults(AuthnRequest authnRequest) {
+		if (authnRequest.getID() == null) {
+			authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
+		}
+		if (authnRequest.getIssueInstant() == null) {
+			authnRequest.setIssueInstant(new DateTime(this.clock.millis()));
+		}
+		if (authnRequest.isForceAuthn() == null) {
+			authnRequest.setForceAuthn(Boolean.FALSE);
+		}
+		if (authnRequest.isPassive() == null) {
+			authnRequest.setIsPassive(Boolean.FALSE);
+		}
+		if (authnRequest.getProtocolBinding() == null) {
+			authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
+		}
+	}
+
+}

+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java


+ 113 - 0
saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlDecryptionUtils.java

@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.EncryptedAttribute;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.encryption.Decrypter;
+import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
+import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
+import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver;
+
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.Saml2X509Credential;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+
+/**
+ * Utility methods for decrypting SAML components with OpenSAML
+ *
+ * For internal use only.
+ *
+ * @author Josh Cummings
+ */
+final class OpenSamlDecryptionUtils {
+
+	private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
+			Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
+					new SimpleRetrievalMethodEncryptedKeyResolver()));
+
+	static void decryptResponseElements(Response response, RelyingPartyRegistration registration) {
+		Decrypter decrypter = decrypter(registration);
+		for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) {
+			try {
+				Assertion assertion = decrypter.decrypt(encryptedAssertion);
+				response.getAssertions().add(assertion);
+			}
+			catch (Exception ex) {
+				throw new Saml2Exception(ex);
+			}
+		}
+	}
+
+	static void decryptAssertionElements(Assertion assertion, RelyingPartyRegistration registration) {
+		Decrypter decrypter = decrypter(registration);
+		for (AttributeStatement statement : assertion.getAttributeStatements()) {
+			for (EncryptedAttribute encryptedAttribute : statement.getEncryptedAttributes()) {
+				try {
+					Attribute attribute = decrypter.decrypt(encryptedAttribute);
+					statement.getAttributes().add(attribute);
+				}
+				catch (Exception ex) {
+					throw new Saml2Exception(ex);
+				}
+			}
+		}
+		if (assertion.getSubject() == null) {
+			return;
+		}
+		if (assertion.getSubject().getEncryptedID() == null) {
+			return;
+		}
+		try {
+			assertion.getSubject().setNameID((NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID()));
+		}
+		catch (Exception ex) {
+			throw new Saml2Exception(ex);
+		}
+	}
+
+	private static Decrypter decrypter(RelyingPartyRegistration registration) {
+		Collection<Credential> credentials = new ArrayList<>();
+		for (Saml2X509Credential key : registration.getDecryptionX509Credentials()) {
+			Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
+			credentials.add(cred);
+		}
+		KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials);
+		Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver);
+		decrypter.setRootInNewDocument(true);
+		return decrypter;
+	}
+
+	private OpenSamlDecryptionUtils() {
+	}
+
+}

+ 173 - 0
saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlSigningUtils.java

@@ -0,0 +1,173 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.xml.SerializeSupport;
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.io.Marshaller;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver;
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.BasicCredential;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.opensaml.xmlsec.SignatureSigningParametersResolver;
+import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion;
+import org.opensaml.xmlsec.crypto.XMLSigningUtil;
+import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
+import org.opensaml.xmlsec.signature.SignableXMLObject;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.opensaml.xmlsec.signature.support.SignatureSupport;
+import org.w3c.dom.Element;
+
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.Saml2X509Credential;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.util.Assert;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriUtils;
+
+/**
+ * Utility methods for signing SAML components with OpenSAML
+ *
+ * For internal use only.
+ *
+ * @author Josh Cummings
+ */
+final class OpenSamlSigningUtils {
+
+	static String serialize(XMLObject object) {
+		try {
+			Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object);
+			Element element = marshaller.marshall(object);
+			return SerializeSupport.nodeToString(element);
+		}
+		catch (MarshallingException ex) {
+			throw new Saml2Exception(ex);
+		}
+	}
+
+	static <O extends SignableXMLObject> O sign(O object, RelyingPartyRegistration relyingPartyRegistration) {
+		SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
+		try {
+			SignatureSupport.signObject(object, parameters);
+			return object;
+		}
+		catch (Exception ex) {
+			throw new Saml2Exception(ex);
+		}
+	}
+
+	static QueryParametersPartial sign(RelyingPartyRegistration registration) {
+		return new QueryParametersPartial(registration);
+	}
+
+	private static SignatureSigningParameters resolveSigningParameters(
+			RelyingPartyRegistration relyingPartyRegistration) {
+		List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
+		List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
+		List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
+		String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
+		SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
+		CriteriaSet criteria = new CriteriaSet();
+		BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration();
+		signingConfiguration.setSigningCredentials(credentials);
+		signingConfiguration.setSignatureAlgorithms(algorithms);
+		signingConfiguration.setSignatureReferenceDigestMethods(digests);
+		signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization);
+		criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
+		try {
+			SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
+			Assert.notNull(parameters, "Failed to resolve any signing credential");
+			return parameters;
+		}
+		catch (Exception ex) {
+			throw new Saml2Exception(ex);
+		}
+	}
+
+	private static List<Credential> resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) {
+		List<Credential> credentials = new ArrayList<>();
+		for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) {
+			X509Certificate certificate = x509Credential.getCertificate();
+			PrivateKey privateKey = x509Credential.getPrivateKey();
+			BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey);
+			credential.setEntityId(relyingPartyRegistration.getEntityId());
+			credential.setUsageType(UsageType.SIGNING);
+			credentials.add(credential);
+		}
+		return credentials;
+	}
+
+	static class QueryParametersPartial {
+
+		final RelyingPartyRegistration registration;
+
+		final Map<String, String> components = new LinkedHashMap<>();
+
+		QueryParametersPartial(RelyingPartyRegistration registration) {
+			this.registration = registration;
+		}
+
+		QueryParametersPartial param(String key, String value) {
+			this.components.put(key, value);
+			return this;
+		}
+
+		Map<String, String> parameters() {
+			SignatureSigningParameters parameters = resolveSigningParameters(this.registration);
+			Credential credential = parameters.getSigningCredential();
+			String algorithmUri = parameters.getSignatureAlgorithm();
+			this.components.put("SigAlg", algorithmUri);
+			UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
+			for (Map.Entry<String, String> component : this.components.entrySet()) {
+				builder.queryParam(component.getKey(),
+						UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1));
+			}
+			String queryString = builder.build(true).toString().substring(1);
+			try {
+				byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri,
+						queryString.getBytes(StandardCharsets.UTF_8));
+				String b64Signature = Saml2Utils.samlEncode(rawSignature);
+				this.components.put("Signature", b64Signature);
+			}
+			catch (SecurityException ex) {
+				throw new Saml2Exception(ex);
+			}
+			return this.components;
+		}
+
+	}
+
+	private OpenSamlSigningUtils() {
+
+	}
+
+}

+ 217 - 0
saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlVerificationUtils.java

@@ -0,0 +1,217 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.RequestAbstractType;
+import org.opensaml.saml.saml2.core.StatusResponseType;
+import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
+import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
+import org.opensaml.security.credential.impl.CollectionCredentialResolver;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.x509.BasicX509Credential;
+import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
+import org.opensaml.xmlsec.signature.Signature;
+import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
+import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
+
+import org.springframework.security.saml2.core.Saml2Error;
+import org.springframework.security.saml2.core.Saml2ErrorCodes;
+import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
+import org.springframework.security.saml2.core.Saml2X509Credential;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.web.util.UriUtils;
+
+/**
+ * Utility methods for verifying SAML component signatures with OpenSAML
+ *
+ * For internal use only.
+ *
+ * @author Josh Cummings
+ */
+
+final class OpenSamlVerificationUtils {
+
+	static VerifierPartial verifySignature(StatusResponseType object, RelyingPartyRegistration registration) {
+		return new VerifierPartial(object, registration);
+	}
+
+	static VerifierPartial verifySignature(RequestAbstractType object, RelyingPartyRegistration registration) {
+		return new VerifierPartial(object, registration);
+	}
+
+	static SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) {
+		Set<Credential> credentials = new HashSet<>();
+		Collection<Saml2X509Credential> keys = registration.getAssertingPartyDetails().getVerificationX509Credentials();
+		for (Saml2X509Credential key : keys) {
+			BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
+			cred.setUsageType(UsageType.SIGNING);
+			cred.setEntityId(registration.getAssertingPartyDetails().getEntityId());
+			credentials.add(cred);
+		}
+		CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
+		return new ExplicitKeySignatureTrustEngine(credentialsResolver,
+				DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
+	}
+
+	static class VerifierPartial {
+
+		private final String id;
+
+		private final CriteriaSet criteria;
+
+		private final SignatureTrustEngine trustEngine;
+
+		VerifierPartial(StatusResponseType object, RelyingPartyRegistration registration) {
+			this.id = object.getID();
+			this.criteria = verificationCriteria(object.getIssuer());
+			this.trustEngine = trustEngine(registration);
+		}
+
+		VerifierPartial(RequestAbstractType object, RelyingPartyRegistration registration) {
+			this.id = object.getID();
+			this.criteria = verificationCriteria(object.getIssuer());
+			this.trustEngine = trustEngine(registration);
+		}
+
+		Saml2ResponseValidatorResult redirect(HttpServletRequest request, String objectParameterName) {
+			RedirectSignature signature = new RedirectSignature(request, objectParameterName);
+			if (signature.getAlgorithm() == null) {
+				return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+						"Missing signature algorithm for object [" + this.id + "]"));
+			}
+			if (!signature.hasSignature()) {
+				return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+						"Missing signature for object [" + this.id + "]"));
+			}
+			Collection<Saml2Error> errors = new ArrayList<>();
+			String algorithmUri = signature.getAlgorithm();
+			try {
+				if (!this.trustEngine.validate(signature.getSignature(), signature.getContent(), algorithmUri,
+						this.criteria, null)) {
+					errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+							"Invalid signature for object [" + this.id + "]"));
+				}
+			}
+			catch (Exception ex) {
+				errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+						"Invalid signature for object [" + this.id + "]: "));
+			}
+			return Saml2ResponseValidatorResult.failure(errors);
+		}
+
+		Saml2ResponseValidatorResult post(Signature signature) {
+			Collection<Saml2Error> errors = new ArrayList<>();
+			SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
+			try {
+				profileValidator.validate(signature);
+			}
+			catch (Exception ex) {
+				errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+						"Invalid signature for object [" + this.id + "]: "));
+			}
+
+			try {
+				if (!this.trustEngine.validate(signature, this.criteria)) {
+					errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+							"Invalid signature for object [" + this.id + "]"));
+				}
+			}
+			catch (Exception ex) {
+				errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
+						"Invalid signature for object [" + this.id + "]: "));
+			}
+
+			return Saml2ResponseValidatorResult.failure(errors);
+		}
+
+		private CriteriaSet verificationCriteria(Issuer issuer) {
+			CriteriaSet criteria = new CriteriaSet();
+			criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())));
+			criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
+			criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
+			return criteria;
+		}
+
+		private static class RedirectSignature {
+
+			private final HttpServletRequest request;
+
+			private final String objectParameterName;
+
+			RedirectSignature(HttpServletRequest request, String objectParameterName) {
+				this.request = request;
+				this.objectParameterName = objectParameterName;
+			}
+
+			String getAlgorithm() {
+				return this.request.getParameter("SigAlg");
+			}
+
+			byte[] getContent() {
+				if (this.request.getParameter("RelayState") != null) {
+					return String.format("%s=%s&RelayState=%s&SigAlg=%s", this.objectParameterName,
+							UriUtils.encode(this.request.getParameter(this.objectParameterName),
+									StandardCharsets.ISO_8859_1),
+							UriUtils.encode(this.request.getParameter("RelayState"), StandardCharsets.ISO_8859_1),
+							UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1))
+							.getBytes(StandardCharsets.UTF_8);
+				}
+				else {
+					return String
+							.format("%s=%s&SigAlg=%s", this.objectParameterName,
+									UriUtils.encode(this.request.getParameter(this.objectParameterName),
+											StandardCharsets.ISO_8859_1),
+									UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1))
+							.getBytes(StandardCharsets.UTF_8);
+				}
+			}
+
+			byte[] getSignature() {
+				return Saml2Utils.samlDecode(this.request.getParameter("Signature"));
+			}
+
+			boolean hasSignature() {
+				return this.request.getParameter("Signature") != null;
+			}
+
+		}
+
+	}
+
+	private OpenSamlVerificationUtils() {
+
+	}
+
+}

+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java


+ 1 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java

@@ -57,4 +57,5 @@ public enum Saml2MessageBinding {
 		}
 		return null;
 	}
+
 }

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


+ 18 - 2
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java

@@ -24,6 +24,8 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.opensaml.core.Version;
+
 import org.springframework.http.MediaType;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
 import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory;
@@ -39,6 +41,7 @@ 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.util.ClassUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.util.HtmlUtils;
@@ -88,8 +91,21 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
 	public Saml2WebSsoAuthenticationRequestFilter(
 			RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
 		this(new DefaultSaml2AuthenticationRequestContextResolver(
-				new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)),
-				new org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory());
+				new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)), requestFactory());
+	}
+
+	private static Saml2AuthenticationRequestFactory requestFactory() {
+		String opensamlClassName = "org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory";
+		if (Version.getVersion().startsWith("4")) {
+			opensamlClassName = "org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationRequestFactory";
+		}
+		try {
+			return (Saml2AuthenticationRequestFactory) ClassUtils.forName(opensamlClassName, null)
+					.getDeclaredConstructor().newInstance();
+		}
+		catch (Exception ex) {
+			throw new IllegalStateException(ex);
+		}
 	}
 
 	/**

+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java


+ 0 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java → saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java


+ 1 - 20
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java

@@ -28,21 +28,17 @@ import javax.crypto.spec.SecretKeySpec;
 import javax.xml.namespace.QName;
 
 import org.apache.xml.security.encryption.XMLCipherParameters;
-import org.joda.time.DateTime;
-import org.joda.time.Duration;
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 import org.opensaml.core.xml.io.MarshallingException;
 import org.opensaml.core.xml.schema.XSAny;
 import org.opensaml.core.xml.schema.XSBoolean;
 import org.opensaml.core.xml.schema.XSBooleanValue;
-import org.opensaml.core.xml.schema.XSDateTime;
 import org.opensaml.core.xml.schema.XSInteger;
 import org.opensaml.core.xml.schema.XSString;
 import org.opensaml.core.xml.schema.XSURI;
 import org.opensaml.core.xml.schema.impl.XSAnyBuilder;
 import org.opensaml.core.xml.schema.impl.XSBooleanBuilder;
-import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder;
 import org.opensaml.core.xml.schema.impl.XSIntegerBuilder;
 import org.opensaml.core.xml.schema.impl.XSStringBuilder;
 import org.opensaml.core.xml.schema.impl.XSURIBuilder;
@@ -114,7 +110,6 @@ public final class TestOpenSamlObjects {
 	static Response response(String destination, String issuerEntityId) {
 		Response response = build(Response.DEFAULT_ELEMENT_NAME);
 		response.setID("R" + UUID.randomUUID().toString());
-		response.setIssueInstant(DateTime.now());
 		response.setVersion(SAMLVersion.VERSION_20);
 		response.setID("_" + UUID.randomUUID().toString());
 		response.setDestination(destination);
@@ -141,9 +136,7 @@ public final class TestOpenSamlObjects {
 	static Assertion assertion(String username, String issuerEntityId, String recipientEntityId, String recipientUri) {
 		Assertion assertion = build(Assertion.DEFAULT_ELEMENT_NAME);
 		assertion.setID("A" + UUID.randomUUID().toString());
-		assertion.setIssueInstant(DateTime.now());
 		assertion.setVersion(SAMLVersion.VERSION_20);
-		assertion.setIssueInstant(DateTime.now());
 		assertion.setIssuer(issuer(issuerEntityId));
 		assertion.setSubject(subject(username));
 		assertion.setConditions(conditions());
@@ -183,16 +176,11 @@ public final class TestOpenSamlObjects {
 	static SubjectConfirmationData subjectConfirmationData(String recipient) {
 		SubjectConfirmationData subject = build(SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
 		subject.setRecipient(recipient);
-		subject.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000)));
-		subject.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000)));
 		return subject;
 	}
 
 	static Conditions conditions() {
-		Conditions conditions = build(Conditions.DEFAULT_ELEMENT_NAME);
-		conditions.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000)));
-		conditions.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000)));
-		return conditions;
+		return build(Conditions.DEFAULT_ELEMENT_NAME);
 	}
 
 	public static AuthnRequest authnRequest() {
@@ -338,13 +326,6 @@ public final class TestOpenSamlObjects {
 		registered.setValue(new XSBooleanValue(true, false));
 		registeredAttr.getAttributeValues().add(registered);
 		attrStmt2.getAttributes().add(registeredAttr);
-		Attribute registeredDateAttr = attributeBuilder.buildObject();
-		registeredDateAttr.setName("registeredDate");
-		XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
-				XSDateTime.TYPE_NAME);
-		registeredDate.setValue(DateTime.parse("1970-01-01T00:00:00Z"));
-		registeredDateAttr.getAttributeValues().add(registeredDate);
-		attrStmt2.getAttributes().add(registeredDateAttr);
 		attributeStatements.add(attrStmt2);
 		return attributeStatements;
 	}

+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java


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


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java


+ 73 - 66
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java

@@ -28,8 +28,10 @@ import org.springframework.mock.web.MockFilterChain;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
+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.authentication.TestSaml2AuthenticationRequestContexts;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
@@ -68,7 +70,7 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
 
 	@Before
 	public void setup() {
-		this.filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
+		this.filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver, this.factory);
 		this.request = new MockHttpServletRequest();
 		this.response = new MockHttpServletResponse();
 		this.request.setPathInfo("/saml2/authenticate/registration-id");
@@ -81,25 +83,48 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
 
 	@Test
 	public void doFilterWhenNoRelayStateThenRedirectDoesNotContainParameter() throws ServletException, IOException {
-		given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().relayState(null).build();
+		Saml2RedirectAuthenticationRequest request = redirectAuthenticationRequest(context).build();
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(this.factory.createRedirectAuthenticationRequest(any())).willReturn(request);
 		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
 		assertThat(this.response.getHeader("Location")).doesNotContain("RelayState=").startsWith(IDP_SSO_URL);
 	}
 
+	private static Saml2AuthenticationRequestContext.Builder authenticationRequestContext() {
+		return TestSaml2AuthenticationRequestContexts.authenticationRequestContext();
+	}
+
+	private static Saml2RedirectAuthenticationRequest.Builder redirectAuthenticationRequest(
+			Saml2AuthenticationRequestContext context) {
+		return Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context).samlRequest("request")
+				.authenticationRequestUri(IDP_SSO_URL);
+	}
+
+	private static Saml2PostAuthenticationRequest.Builder postAuthenticationRequest(
+			Saml2AuthenticationRequestContext context) {
+		return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context).samlRequest("request")
+				.authenticationRequestUri(IDP_SSO_URL);
+	}
+
 	@Test
 	public void doFilterWhenRelayStateThenRedirectDoesContainParameter() throws ServletException, IOException {
-		given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
-		this.request.setParameter("RelayState", "my-relay-state");
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().build();
+		Saml2RedirectAuthenticationRequest request = redirectAuthenticationRequest(context).build();
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(this.factory.createRedirectAuthenticationRequest(any())).willReturn(request);
 		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
-		assertThat(this.response.getHeader("Location")).contains("RelayState=my-relay-state").startsWith(IDP_SSO_URL);
+		assertThat(this.response.getHeader("Location")).contains("RelayState=relayState").startsWith(IDP_SSO_URL);
 	}
 
 	@Test
 	public void doFilterWhenRelayStateThatRequiresEncodingThenRedirectDoesContainsEncodedParameter() throws Exception {
-		given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
-		final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
-		final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
-		this.request.setParameter("RelayState", relayStateValue);
+		String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
+		String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().relayState(relayStateValue).build();
+		Saml2RedirectAuthenticationRequest request = redirectAuthenticationRequest(context).build();
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(this.factory.createRedirectAuthenticationRequest(any())).willReturn(request);
 		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
 		assertThat(this.response.getHeader("Location")).contains("RelayState=" + relayStateEncoded)
 				.startsWith(IDP_SSO_URL);
@@ -107,34 +132,39 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
 
 	@Test
 	public void doFilterWhenSimpleSignatureSpecifiedThenSignatureParametersAreInTheRedirectURL() throws Exception {
-		given(this.repository.findByRegistrationId("registration-id")).willReturn(this.rpBuilder.build());
-		final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
-		final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
-		this.request.setParameter("RelayState", relayStateValue);
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().build();
+		Saml2RedirectAuthenticationRequest request = redirectAuthenticationRequest(context).sigAlg("sigalg")
+				.signature("signature").build();
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(this.factory.createRedirectAuthenticationRequest(any())).willReturn(request);
 		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
-		assertThat(this.response.getHeader("Location")).contains("RelayState=" + relayStateEncoded).contains("SigAlg=")
-				.contains("Signature=").startsWith(IDP_SSO_URL);
+		assertThat(this.response.getHeader("Location")).contains("SigAlg=").contains("Signature=")
+				.startsWith(IDP_SSO_URL);
 	}
 
 	@Test
 	public void doFilterWhenSignatureIsDisabledThenSignatureParametersAreNotInTheRedirectURL() throws Exception {
-		given(this.repository.findByRegistrationId("registration-id"))
-				.willReturn(this.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);
-		this.request.setParameter("RelayState", relayStateValue);
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().build();
+		Saml2RedirectAuthenticationRequest request = redirectAuthenticationRequest(context).build();
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(this.factory.createRedirectAuthenticationRequest(any())).willReturn(request);
 		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
-		assertThat(this.response.getHeader("Location")).contains("RelayState=" + relayStateEncoded)
-				.doesNotContain("SigAlg=").doesNotContain("Signature=").startsWith(IDP_SSO_URL);
+		assertThat(this.response.getHeader("Location")).doesNotContain("SigAlg=").doesNotContain("Signature=")
+				.startsWith(IDP_SSO_URL);
 	}
 
 	@Test
 	public void doFilterWhenPostFormDataIsPresent() throws Exception {
-		given(this.repository.findByRegistrationId("registration-id"))
-				.willReturn(this.rpBuilder.providerDetails((c) -> c.binding(Saml2MessageBinding.POST)).build());
-		final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param&javascript{alert('1');}";
-		final String relayStateEncoded = HtmlUtils.htmlEscape(relayStateValue);
-		this.request.setParameter("RelayState", relayStateValue);
+		String relayStateValue = "https://my-relay-state.example.com?with=param&other=param&javascript{alert('1');}";
+		String relayStateEncoded = HtmlUtils.htmlEscape(relayStateValue);
+		RelyingPartyRegistration registration = this.rpBuilder
+				.assertingPartyDetails((asserting) -> asserting.singleSignOnServiceBinding(Saml2MessageBinding.POST))
+				.build();
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().relayState(relayStateValue)
+				.relyingPartyRegistration(registration).build();
+		Saml2PostAuthenticationRequest request = postAuthenticationRequest(context).build();
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(this.factory.createPostAuthenticationRequest(any())).willReturn(request);
 		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
 		assertThat(this.response.getHeader("Location")).isNull();
 		assertThat(this.response.getContentAsString())
@@ -145,66 +175,43 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
 
 	@Test
 	public void doFilterWhenSetAuthenticationRequestFactoryThenUses() throws Exception {
-		RelyingPartyRegistration relyingParty = this.rpBuilder
-				.providerDetails((c) -> c.binding(Saml2MessageBinding.POST)).build();
-		Saml2PostAuthenticationRequest authenticationRequest = mock(Saml2PostAuthenticationRequest.class);
-		given(authenticationRequest.getAuthenticationRequestUri()).willReturn("uri");
-		given(authenticationRequest.getRelayState()).willReturn("relay");
-		given(authenticationRequest.getSamlRequest()).willReturn("saml");
-		given(this.repository.findByRegistrationId("registration-id")).willReturn(relyingParty);
-		given(this.factory.createPostAuthenticationRequest(any())).willReturn(authenticationRequest);
-		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
-		filter.setAuthenticationRequestFactory(this.factory);
-		filter.doFilterInternal(this.request, this.response, this.filterChain);
-		assertThat(this.response.getContentAsString()).contains("<form action=\"uri\" method=\"post\">")
-				.contains("<input type=\"hidden\" name=\"SAMLRequest\" value=\"saml\"")
-				.contains("<input type=\"hidden\" name=\"RelayState\" value=\"relay\"");
-		verify(this.factory).createPostAuthenticationRequest(any());
+		Saml2AuthenticationRequestContext context = authenticationRequestContext().build();
+		Saml2RedirectAuthenticationRequest authenticationRequest = redirectAuthenticationRequest(context).build();
+		Saml2AuthenticationRequestFactory factory = mock(Saml2AuthenticationRequestFactory.class);
+		given(this.resolver.resolve(any())).willReturn(context);
+		given(factory.createRedirectAuthenticationRequest(any())).willReturn(authenticationRequest);
+		this.filter.setAuthenticationRequestFactory(factory);
+		this.filter.doFilterInternal(this.request, this.response, this.filterChain);
+		verify(factory).createRedirectAuthenticationRequest(any());
 	}
 
 	@Test
-	public void doFilterWhenCustomAuthenticationRequestFactoryThenUses() throws Exception {
-		RelyingPartyRegistration relyingParty = this.rpBuilder
-				.providerDetails((c) -> c.binding(Saml2MessageBinding.POST)).build();
-		Saml2PostAuthenticationRequest authenticationRequest = mock(Saml2PostAuthenticationRequest.class);
-		given(authenticationRequest.getAuthenticationRequestUri()).willReturn("uri");
-		given(authenticationRequest.getRelayState()).willReturn("relay");
-		given(authenticationRequest.getSamlRequest()).willReturn("saml");
-		given(this.resolver.resolve(this.request)).willReturn(TestSaml2AuthenticationRequestContexts
-				.authenticationRequestContext().relyingPartyRegistration(relyingParty).build());
-		given(this.factory.createPostAuthenticationRequest(any())).willReturn(authenticationRequest);
+	public void setRequestMatcherWhenNullThenException() {
 		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver,
 				this.factory);
-		filter.doFilterInternal(this.request, this.response, this.filterChain);
-		assertThat(this.response.getContentAsString()).contains("<form action=\"uri\" method=\"post\">")
-				.contains("<input type=\"hidden\" name=\"SAMLRequest\" value=\"saml\"")
-				.contains("<input type=\"hidden\" name=\"RelayState\" value=\"relay\"");
-		verify(this.factory).createPostAuthenticationRequest(any());
-	}
-
-	@Test
-	public void setRequestMatcherWhenNullThenException() {
-		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
 		assertThatIllegalArgumentException().isThrownBy(() -> filter.setRedirectMatcher(null));
 	}
 
 	@Test
 	public void setAuthenticationRequestFactoryWhenNullThenException() {
-		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
+		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver,
+				this.factory);
 		assertThatIllegalArgumentException().isThrownBy(() -> filter.setAuthenticationRequestFactory(null));
 	}
 
 	@Test
 	public void doFilterWhenRequestMatcherFailsThenSkipsFilter() throws Exception {
-		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
+		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver,
+				this.factory);
 		filter.setRedirectMatcher((request) -> false);
 		filter.doFilter(this.request, this.response, this.filterChain);
-		verifyNoInteractions(this.repository);
+		verifyNoInteractions(this.resolver, this.factory);
 	}
 
 	@Test
 	public void doFilterWhenRelyingPartyRegistrationNotFoundThenUnauthorized() throws Exception {
-		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.repository);
+		Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(this.resolver,
+				this.factory);
 		filter.doFilter(this.request, this.response, this.filterChain);
 		assertThat(this.response.getStatus()).isEqualTo(401);
 	}

+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java → saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java


+ 0 - 0
saml2/saml2-service-provider/src/test/resources/logback-test.xml → saml2/saml2-service-provider/core/src/test/resources/logback-test.xml


+ 0 - 0
saml2/saml2-service-provider/src/test/resources/saml2-response-sso-circle.encoded → saml2/saml2-service-provider/core/src/test/resources/saml2-response-sso-circle.encoded


+ 0 - 0
saml2/saml2-service-provider/src/test/resources/test-metadata.xml → saml2/saml2-service-provider/core/src/test/resources/test-metadata.xml


+ 53 - 0
saml2/saml2-service-provider/opensaml3/saml2-service-provider-opensaml3.gradle

@@ -0,0 +1,53 @@
+buildscript {
+	repositories {
+		maven { url 'https://repo.spring.io/plugins-release' }
+	}
+	dependencies {
+		classpath 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
+	}
+}
+
+plugins {
+	id 'java-library'
+	id 'io.spring.convention.repository'
+	id 'io.spring.convention.springdependencymangement'
+	id 'io.spring.convention.dependency-set'
+	id 'io.spring.convention.checkstyle'
+	id 'io.spring.convention.tests-configuration'
+	id 'io.spring.convention.integration-test'
+	id 'propdeps'
+}
+
+configurations {
+	classesOnlyElements {
+		canBeConsumed = true
+		canBeResolved = false
+	}
+}
+
+artifacts {
+	classesOnlyElements(compileJava.destinationDir)
+}
+
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
+dependencies {
+	constraints {
+		management("org.opensaml:opensaml-core:3.+")
+		management("org.opensaml:opensaml-saml-api:3.+")
+		management("org.opensaml:opensaml-saml-impl:3.+")
+	}
+
+	compile project(':saml2-service-provider-core')
+
+	compile("org.opensaml:opensaml-core")
+	compile("org.opensaml:opensaml-saml-api")
+	compile("org.opensaml:opensaml-saml-impl")
+
+	provided 'javax.servlet:javax.servlet-api'
+
+	testCompile 'com.squareup.okhttp3:mockwebserver'
+	testCompile project(path : ':saml2-service-provider-core', configuration : 'tests')
+}

+ 21 - 141
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java → saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java

@@ -21,27 +21,21 @@ import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Consumer;
 
 import javax.annotation.Nonnull;
 import javax.xml.namespace.QName;
 
-import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 import net.shibboleth.utilities.java.support.xml.ParserPool;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.joda.time.DateTime;
 import org.opensaml.core.config.ConfigurationService;
-import org.opensaml.core.criterion.EntityIdCriterion;
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
 import org.opensaml.core.xml.schema.XSAny;
@@ -51,11 +45,9 @@ import org.opensaml.core.xml.schema.XSDateTime;
 import org.opensaml.core.xml.schema.XSInteger;
 import org.opensaml.core.xml.schema.XSString;
 import org.opensaml.core.xml.schema.XSURI;
+import org.opensaml.saml.common.assertion.AssertionValidationException;
 import org.opensaml.saml.common.assertion.ValidationContext;
 import org.opensaml.saml.common.assertion.ValidationResult;
-import org.opensaml.saml.common.xml.SAMLConstants;
-import org.opensaml.saml.criterion.ProtocolCriterion;
-import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
 import org.opensaml.saml.saml2.assertion.ConditionValidator;
 import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
 import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
@@ -69,35 +61,15 @@ import org.opensaml.saml.saml2.core.Attribute;
 import org.opensaml.saml.saml2.core.AttributeStatement;
 import org.opensaml.saml.saml2.core.Condition;
 import org.opensaml.saml.saml2.core.EncryptedAssertion;
-import org.opensaml.saml.saml2.core.EncryptedAttribute;
-import org.opensaml.saml.saml2.core.NameID;
 import org.opensaml.saml.saml2.core.OneTimeUse;
 import org.opensaml.saml.saml2.core.Response;
 import org.opensaml.saml.saml2.core.StatusCode;
 import org.opensaml.saml.saml2.core.SubjectConfirmation;
 import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
 import org.opensaml.saml.saml2.encryption.Decrypter;
-import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
 import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
-import org.opensaml.security.credential.Credential;
-import org.opensaml.security.credential.CredentialResolver;
-import org.opensaml.security.credential.CredentialSupport;
-import org.opensaml.security.credential.UsageType;
-import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
-import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
-import org.opensaml.security.credential.impl.CollectionCredentialResolver;
-import org.opensaml.security.criteria.UsageCriterion;
-import org.opensaml.security.x509.BasicX509Credential;
-import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
-import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
-import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
-import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
-import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
-import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
-import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver;
 import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
 import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
-import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -115,7 +87,7 @@ import org.springframework.security.saml2.core.OpenSamlInitializationService;
 import org.springframework.security.saml2.core.Saml2Error;
 import org.springframework.security.saml2.core.Saml2ErrorCodes;
 import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
-import org.springframework.security.saml2.core.Saml2X509Credential;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
@@ -165,6 +137,8 @@ import org.springframework.util.StringUtils;
  * "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38">SAML 2
  * StatusResponse</a>
  * @see <a href="https://wiki.shibboleth.net/confluence/display/OS30/Home">OpenSAML 3</a>
+ * @deprecated Because OpenSAML 3 has reached End-of-Life, please update to
+ * {@link OpenSaml4AuthenticationProvider}
  */
 public final class OpenSamlAuthenticationProvider implements AuthenticationProvider {
 
@@ -201,10 +175,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 
 	private Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter = createCompatibleResponseAuthenticationConverter();
 
-	private Converter<Saml2AuthenticationToken, SignatureTrustEngine> signatureTrustEngineConverter = new SignatureTrustEngineConverter();
-
-	private Converter<Saml2AuthenticationToken, Decrypter> decrypterConverter = new DecrypterConverter();
-
 	/**
 	 * Creates an {@link OpenSamlAuthenticationProvider}
 	 */
@@ -560,53 +530,23 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 	private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseSignatureValidator() {
 		return (responseToken) -> {
 			Response response = responseToken.getResponse();
-			Saml2AuthenticationToken token = responseToken.getToken();
-			Collection<Saml2Error> errors = new ArrayList<>();
-			String issuer = response.getIssuer().getValue();
+			RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration();
 			if (response.isSigned()) {
-				SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
-				try {
-					profileValidator.validate(response.getSignature());
-				}
-				catch (Exception ex) {
-					errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
-							"Invalid signature for SAML Response [" + response.getID() + "]: "));
-				}
-
-				try {
-					CriteriaSet criteriaSet = new CriteriaSet();
-					criteriaSet.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer)));
-					criteriaSet.add(new EvaluableProtocolRoleDescriptorCriterion(
-							new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
-					criteriaSet.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
-					if (!this.signatureTrustEngineConverter.convert(token).validate(response.getSignature(),
-							criteriaSet)) {
-						errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
-								"Invalid signature for SAML Response [" + response.getID() + "]"));
-					}
-				}
-				catch (Exception ex) {
-					errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
-							"Invalid signature for SAML Response [" + response.getID() + "]: "));
-				}
+				return OpenSamlVerificationUtils.verifySignature(response, registration).post(response.getSignature());
 			}
-
-			return Saml2ResponseValidatorResult.failure(errors);
+			return Saml2ResponseValidatorResult.success();
 		};
 	}
 
 	private Consumer<ResponseToken> createDefaultResponseElementsDecrypter() {
 		return (responseToken) -> {
-			Decrypter decrypter = this.decrypterConverter.convert(responseToken.getToken());
 			Response response = responseToken.getResponse();
-			for (EncryptedAssertion encryptedAssertion : responseToken.getResponse().getEncryptedAssertions()) {
-				try {
-					Assertion assertion = decrypter.decrypt(encryptedAssertion);
-					response.getAssertions().add(assertion);
-				}
-				catch (Exception ex) {
-					throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
-				}
+			RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration();
+			try {
+				OpenSamlDecryptionUtils.decryptResponseElements(response, registration);
+			}
+			catch (Saml2Exception ex) {
+				throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
 			}
 		};
 	}
@@ -656,7 +596,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 
 	private Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionSignatureValidator() {
 		return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> {
-			SignatureTrustEngine engine = this.signatureTrustEngineConverter.convert(assertionToken.token);
+			RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration();
+			SignatureTrustEngine engine = OpenSamlVerificationUtils.trustEngine(registration);
 			return SAML20AssertionValidators.createSignatureValidator(engine);
 		}, (assertionToken) -> new ValidationContext(
 				Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false)));
@@ -664,29 +605,12 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 
 	private Consumer<AssertionToken> createDefaultAssertionElementsDecrypter() {
 		return (assertionToken) -> {
-			Decrypter decrypter = this.decrypterConverter.convert(assertionToken.getToken());
 			Assertion assertion = assertionToken.getAssertion();
-			for (AttributeStatement statement : assertion.getAttributeStatements()) {
-				for (EncryptedAttribute encryptedAttribute : statement.getEncryptedAttributes()) {
-					try {
-						Attribute attribute = decrypter.decrypt(encryptedAttribute);
-						statement.getAttributes().add(attribute);
-					}
-					catch (Exception ex) {
-						throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
-					}
-				}
-			}
-			if (assertion.getSubject() == null) {
-				return;
-			}
-			if (assertion.getSubject().getEncryptedID() == null) {
-				return;
-			}
+			RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration();
 			try {
-				assertion.getSubject().setNameID((NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID()));
+				OpenSamlDecryptionUtils.decryptAssertionElements(assertion, registration);
 			}
-			catch (Exception ex) {
+			catch (Saml2Exception ex) {
 				throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
 			}
 		};
@@ -765,8 +689,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 			return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null;
 		}
 		if (xmlObject instanceof XSDateTime) {
-			DateTime dateTime = ((XSDateTime) xmlObject).getValue();
-			return (dateTime != null) ? Instant.ofEpochMilli(dateTime.getMillis()) : null;
+			return Instant.ofEpochMilli(((XSDateTime) xmlObject).getValue().getMillis());
 		}
 		return null;
 	}
@@ -812,27 +735,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 		return new ValidationContext(params);
 	}
 
-	private static class SignatureTrustEngineConverter
-			implements Converter<Saml2AuthenticationToken, SignatureTrustEngine> {
-
-		@Override
-		public SignatureTrustEngine convert(Saml2AuthenticationToken token) {
-			Set<Credential> credentials = new HashSet<>();
-			Collection<Saml2X509Credential> keys = token.getRelyingPartyRegistration().getAssertingPartyDetails()
-					.getVerificationX509Credentials();
-			for (Saml2X509Credential key : keys) {
-				BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
-				cred.setUsageType(UsageType.SIGNING);
-				cred.setEntityId(token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId());
-				credentials.add(cred);
-			}
-			CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
-			return new ExplicitKeySignatureTrustEngine(credentialsResolver,
-					DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
-		}
-
-	}
-
 	private static class SAML20AssertionValidators {
 
 		private static final Collection<ConditionValidator> conditions = new ArrayList<>();
@@ -861,10 +763,9 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 				}
 			});
 			subjects.add(new BearerSubjectConfirmationValidator() {
-				@Nonnull
 				@Override
-				protected ValidationResult validateAddress(@Nonnull SubjectConfirmation confirmation,
-						@Nonnull Assertion assertion, @Nonnull ValidationContext context) {
+				protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion,
+						ValidationContext context) throws AssertionValidationException {
 					// applications should validate their own addresses - gh-7514
 					return ValidationResult.VALID;
 				}
@@ -906,27 +807,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 
 	}
 
-	private static class DecrypterConverter implements Converter<Saml2AuthenticationToken, Decrypter> {
-
-		private final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
-				Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
-						new SimpleRetrievalMethodEncryptedKeyResolver()));
-
-		@Override
-		public Decrypter convert(Saml2AuthenticationToken token) {
-			Collection<Credential> credentials = new ArrayList<>();
-			for (Saml2X509Credential key : token.getRelyingPartyRegistration().getDecryptionX509Credentials()) {
-				Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
-				credentials.add(cred);
-			}
-			KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials);
-			Decrypter decrypter = new Decrypter(null, resolver, this.encryptedKeyResolver);
-			decrypter.setRootInNewDocument(true);
-			return decrypter;
-		}
-
-	}
-
 	/**
 	 * A tuple containing an OpenSAML {@link Response} and its associated authentication
 	 * token.

+ 203 - 0
saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java

@@ -0,0 +1,203 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
+import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.saml2.core.OpenSamlInitializationService;
+import org.springframework.security.saml2.provider.service.authentication.OpenSamlSigningUtils.QueryParametersPartial;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link Saml2AuthenticationRequestFactory} that generates, signs, and serializes a
+ * SAML 2.0 AuthnRequest using OpenSAML 3
+ *
+ * @author Filip Hanik
+ * @author Josh Cummings
+ * @since 5.2
+ * @deprecated Because OpenSAML 3 has reached End-of-Life, please update to
+ * {@link OpenSaml4AuthenticationRequestFactory}
+ */
+public class OpenSamlAuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
+
+	static {
+		OpenSamlInitializationService.initialize();
+	}
+
+	private AuthnRequestBuilder authnRequestBuilder;
+
+	private IssuerBuilder issuerBuilder;
+
+	private Clock clock = Clock.systemUTC();
+
+	private Converter<Saml2AuthenticationRequestContext, Saml2MessageBinding> protocolBindingResolver = (context) -> {
+		if (context == null) {
+			return Saml2MessageBinding.POST;
+		}
+		return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding();
+	};
+
+	private Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter;
+
+	/**
+	 * Creates an {@link OpenSamlAuthenticationRequestFactory}
+	 */
+	public OpenSamlAuthenticationRequestFactory() {
+		this.authenticationRequestContextConverter = this::createAuthnRequest;
+		XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
+		this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory()
+				.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
+		this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+	}
+
+	@Override
+	@Deprecated
+	public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
+		Saml2MessageBinding binding = this.protocolBindingResolver.convert(null);
+		RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("noId")
+				.assertionConsumerServiceBinding(binding)
+				.assertionConsumerServiceLocation(request.getAssertionConsumerServiceUrl())
+				.entityId(request.getIssuer()).remoteIdpEntityId("noIssuer").idpWebSsoUrl("noUrl")
+				.credentials((credentials) -> credentials.addAll(request.getCredentials())).build();
+		Saml2AuthenticationRequestContext context = Saml2AuthenticationRequestContext.builder()
+				.relyingPartyRegistration(registration).issuer(request.getIssuer())
+				.assertionConsumerServiceUrl(request.getAssertionConsumerServiceUrl()).build();
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		return OpenSamlSigningUtils.serialize(OpenSamlSigningUtils.sign(authnRequest, registration));
+	}
+
+	@Override
+	public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		RelyingPartyRegistration registration = context.getRelyingPartyRegistration();
+		if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
+			OpenSamlSigningUtils.sign(authnRequest, registration);
+		}
+		String xml = OpenSamlSigningUtils.serialize(authnRequest);
+		return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
+				.samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build();
+	}
+
+	@Override
+	public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
+			Saml2AuthenticationRequestContext context) {
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		RelyingPartyRegistration registration = context.getRelyingPartyRegistration();
+		String xml = OpenSamlSigningUtils.serialize(authnRequest);
+		Saml2RedirectAuthenticationRequest.Builder result = Saml2RedirectAuthenticationRequest
+				.withAuthenticationRequestContext(context);
+		String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
+		result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState());
+		if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
+			QueryParametersPartial partial = OpenSamlSigningUtils.sign(registration).param("SAMLRequest",
+					deflatedAndEncoded);
+			if (StringUtils.hasText(context.getRelayState())) {
+				partial.param("RelayState", context.getRelayState());
+			}
+			Map<String, String> parameters = partial.parameters();
+			return result.sigAlg(parameters.get("SigAlg")).signature(parameters.get("Signature")).build();
+		}
+		return result.build();
+	}
+
+	private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) {
+		String issuer = context.getIssuer();
+		String destination = context.getDestination();
+		String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();
+		Saml2MessageBinding protocolBinding = this.protocolBindingResolver.convert(context);
+		AuthnRequest auth = this.authnRequestBuilder.buildObject();
+		if (auth.getID() == null) {
+			auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
+		}
+		if (auth.getIssueInstant() == null) {
+			auth.setIssueInstant(new DateTime(this.clock.millis()));
+		}
+		if (auth.isForceAuthn() == null) {
+			auth.setForceAuthn(Boolean.FALSE);
+		}
+		if (auth.isPassive() == null) {
+			auth.setIsPassive(Boolean.FALSE);
+		}
+		if (auth.getProtocolBinding() == null) {
+			auth.setProtocolBinding(protocolBinding.getUrn());
+		}
+		Issuer iss = this.issuerBuilder.buildObject();
+		iss.setValue(issuer);
+		auth.setIssuer(iss);
+		auth.setDestination(destination);
+		auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
+		return auth;
+	}
+
+	/**
+	 * Set the {@link AuthnRequest} post-processor resolver
+	 * @param authenticationRequestContextConverter
+	 * @since 5.4
+	 */
+	public void setAuthenticationRequestContextConverter(
+			Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter) {
+		Assert.notNull(authenticationRequestContextConverter, "authenticationRequestContextConverter cannot be null");
+		this.authenticationRequestContextConverter = authenticationRequestContextConverter;
+	}
+
+	/**
+	 * ' Use this {@link Clock} with {@link Instant#now()} for generating timestamps
+	 * @param clock
+	 */
+	public void setClock(Clock clock) {
+		Assert.notNull(clock, "clock cannot be null");
+		this.clock = clock;
+	}
+
+	/**
+	 * Sets the {@code protocolBinding} to use when generating authentication requests.
+	 * Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and
+	 * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} The IDP will be reading this value
+	 * in the {@code AuthNRequest} to determine how to send the Response/Assertion to the
+	 * ACS URL, assertion consumer service URL.
+	 * @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or
+	 * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
+	 * @throws IllegalArgumentException if the protocolBinding is not valid
+	 * @deprecated Use
+	 * {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder#assertionConsumerServiceBinding(Saml2MessageBinding)}
+	 * instead
+	 */
+	@Deprecated
+	public void setProtocolBinding(String protocolBinding) {
+		Saml2MessageBinding binding = Saml2MessageBinding.from(protocolBinding);
+		Assert.notNull(binding, "Invalid protocol binding: " + protocolBinding);
+		this.protocolBindingResolver = (context) -> binding;
+	}
+
+}

+ 94 - 47
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java → saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java

@@ -38,10 +38,15 @@ import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
 import org.opensaml.core.xml.io.Marshaller;
 import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder;
 import org.opensaml.saml.common.assertion.ValidationContext;
 import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
 import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
 import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.AttributeValue;
+import org.opensaml.saml.saml2.core.Conditions;
 import org.opensaml.saml.saml2.core.EncryptedAssertion;
 import org.opensaml.saml.saml2.core.EncryptedAttribute;
 import org.opensaml.saml.saml2.core.EncryptedID;
@@ -49,6 +54,9 @@ import org.opensaml.saml.saml2.core.NameID;
 import org.opensaml.saml.saml2.core.OneTimeUse;
 import org.opensaml.saml.saml2.core.Response;
 import org.opensaml.saml.saml2.core.StatusCode;
+import org.opensaml.saml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml.saml2.core.SubjectConfirmationData;
+import org.opensaml.saml.saml2.core.impl.AttributeBuilder;
 import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder;
 import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder;
 import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
@@ -134,8 +142,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
-		response.getAssertions().add(TestOpenSamlObjects.assertion());
+		Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion());
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
 		Saml2AuthenticationToken token = token(response, verifying(registration()));
@@ -154,8 +162,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		response.getAssertions().add(TestOpenSamlObjects.assertion());
+		Response response = response();
+		response.getAssertions().add(assertion());
 		Saml2AuthenticationToken token = token(response, verifying(registration()));
 		assertThatExceptionOfType(Saml2AuthenticationException.class)
 				.isThrownBy(() -> this.provider.authenticate(token))
@@ -164,8 +172,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData()
 				.setNotOnOrAfter(DateTime.now().minus(Duration.standardDays(3)));
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -179,8 +187,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenMissingSubjectThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.setSubject(null);
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
@@ -193,8 +201,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenUsernameMissingThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.getSubject().getNameID().setValue(null);
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
@@ -207,8 +215,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		assertion.getSubject().getSubjectConfirmations()
 				.forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10"));
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -220,9 +228,9 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenAssertionContainsAttributesThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
-		List<AttributeStatement> attributes = TestOpenSamlObjects.attributeStatements();
+		Response response = response();
+		Assertion assertion = assertion();
+		List<AttributeStatement> attributes = attributeStatements();
 		assertion.getAttributeStatements().addAll(attributes);
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
@@ -244,8 +252,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -258,8 +266,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
 				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
 		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
@@ -272,8 +280,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -284,8 +292,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		NameID nameId = assertion.getSubject().getNameID();
 		EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId,
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
@@ -300,8 +308,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenEncryptedAttributeThenDecrypts() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value",
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME);
@@ -318,8 +326,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -332,8 +340,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() {
-		Response response = TestOpenSamlObjects.response();
-		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
 		response.getEncryptedAssertions().add(encryptedAssertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
@@ -347,8 +355,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
 				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
 		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
 				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
@@ -384,8 +392,8 @@ public class OpenSamlAuthenticationProviderTests {
 				.concat(new Saml2Error("wrong error", "wrong error"))
 		);
 		// @formatter:on
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME);
 		assertion.getConditions().getConditions().add(oneTimeUse);
 		response.getAssertions().add(assertion);
@@ -410,8 +418,8 @@ public class OpenSamlAuthenticationProviderTests {
 				.concat(validator.convert(assertionToken))
 		);
 		// @formatter:on
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		response.getAssertions().add(assertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				ASSERTING_PARTY_ENTITY_ID);
@@ -426,8 +434,8 @@ public class OpenSamlAuthenticationProviderTests {
 	public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() {
 		OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 		provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success());
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(),
 				RELYING_PARTY_ENTITY_ID); // broken
 		// signature
@@ -451,8 +459,8 @@ public class OpenSamlAuthenticationProviderTests {
 		OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
 		provider.setAssertionValidator(
 				OpenSamlAuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context));
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		response.getAssertions().add(assertion);
 		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				ASSERTING_PARTY_ENTITY_ID);
@@ -467,8 +475,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.signed(TestOpenSamlObjects.assertion(),
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
 				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID,
 				SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
 		response.getAssertions().add(assertion);
@@ -525,8 +533,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
 				RELYING_PARTY_ENTITY_ID);
 		response.getEncryptedAssertions().add(new EncryptedAssertionBuilder().buildObject());
@@ -540,8 +548,8 @@ public class OpenSamlAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertion() {
-		Response response = TestOpenSamlObjects.response();
-		Assertion assertion = TestOpenSamlObjects.assertion();
+		Response response = response();
+		Assertion assertion = assertion();
 		EncryptedID id = new EncryptedIDBuilder().buildObject();
 		id.setEncryptedData(new EncryptedDataBuilder().buildObject());
 		assertion.getSubject().setEncryptedID(id);
@@ -600,13 +608,52 @@ public class OpenSamlAuthenticationProviderTests {
 		return (ex) -> {
 			assertThat(ex.getError().getErrorCode()).isEqualTo(errorCode);
 			if (StringUtils.hasText(description)) {
-				assertThat(ex.getError().getDescription()).isEqualTo(description);
+				assertThat(ex.getError().getDescription()).contains(description);
 			}
 		};
 	}
 
-	private Saml2AuthenticationToken token() {
+	private Response response() {
 		Response response = TestOpenSamlObjects.response();
+		response.setIssueInstant(DateTime.now());
+		return response;
+	}
+
+	private Response response(String destination, String issuerEntityId) {
+		Response response = TestOpenSamlObjects.response(destination, issuerEntityId);
+		response.setIssueInstant(DateTime.now());
+		return response;
+	}
+
+	private Assertion assertion() {
+		Assertion assertion = TestOpenSamlObjects.assertion();
+		assertion.setIssueInstant(DateTime.now());
+		for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) {
+			SubjectConfirmationData data = confirmation.getSubjectConfirmationData();
+			data.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000)));
+			data.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000)));
+		}
+		Conditions conditions = assertion.getConditions();
+		conditions.setNotBefore(DateTime.now().minus(Duration.millis(5 * 60 * 1000)));
+		conditions.setNotOnOrAfter(DateTime.now().plus(Duration.millis(5 * 60 * 1000)));
+		return assertion;
+	}
+
+	private List<AttributeStatement> attributeStatements() {
+		List<AttributeStatement> attributeStatements = TestOpenSamlObjects.attributeStatements();
+		AttributeBuilder attributeBuilder = new AttributeBuilder();
+		Attribute registeredDateAttr = attributeBuilder.buildObject();
+		registeredDateAttr.setName("registeredDate");
+		XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
+				XSDateTime.TYPE_NAME);
+		registeredDate.setValue(DateTime.parse("1970-01-01T00:00:00Z"));
+		registeredDateAttr.getAttributeValues().add(registeredDate);
+		attributeStatements.get(0).getAttributes().add(registeredDateAttr);
+		return attributeStatements;
+	}
+
+	private Saml2AuthenticationToken token() {
+		Response response = response();
 		RelyingPartyRegistration registration = verifying(registration()).build();
 		return new Saml2AuthenticationToken(registration, serialize(response));
 	}

+ 9 - 4
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java → saml2/saml2-service-provider/opensaml3/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java

@@ -19,6 +19,7 @@ package org.springframework.security.saml2.provider.service.authentication;
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
 
+import org.joda.time.DateTime;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -200,8 +201,7 @@ public class OpenSamlAuthenticationRequestFactoryTests {
 	public void createPostAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
 		Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = mock(
 				Converter.class);
-		given(authenticationRequestContextConverter.convert(this.context))
-				.willReturn(TestOpenSamlObjects.authnRequest());
+		given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest());
 		this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
 
 		this.factory.createPostAuthenticationRequest(this.context);
@@ -212,8 +212,7 @@ public class OpenSamlAuthenticationRequestFactoryTests {
 	public void createRedirectAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
 		Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = mock(
 				Converter.class);
-		given(authenticationRequestContextConverter.convert(this.context))
-				.willReturn(TestOpenSamlObjects.authnRequest());
+		given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest());
 		this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
 
 		this.factory.createRedirectAuthenticationRequest(this.context);
@@ -256,6 +255,12 @@ public class OpenSamlAuthenticationRequestFactoryTests {
 		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
 	}
 
+	private AuthnRequest authnRequest() {
+		AuthnRequest authnRequest = TestOpenSamlObjects.authnRequest();
+		authnRequest.setIssueInstant(DateTime.now());
+		return authnRequest;
+	}
+
 	private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) {
 		AbstractSaml2AuthenticationRequest result = (binding == Saml2MessageBinding.REDIRECT)
 				? this.factory.createRedirectAuthenticationRequest(this.context)

+ 58 - 0
saml2/saml2-service-provider/opensaml4/saml2-service-provider-opensaml4.gradle

@@ -0,0 +1,58 @@
+buildscript {
+	repositories {
+		maven { url 'https://repo.spring.io/plugins-release' }
+	}
+	dependencies {
+		classpath 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
+	}
+}
+
+plugins {
+	id 'java-library'
+	id 'io.spring.convention.repository'
+	id 'io.spring.convention.springdependencymangement'
+	id 'io.spring.convention.dependency-set'
+	id 'io.spring.convention.checkstyle'
+	id 'io.spring.convention.tests-configuration'
+	id 'io.spring.convention.integration-test'
+	id 'propdeps'
+}
+
+configurations {
+	classesOnlyElements {
+		canBeConsumed = true
+		canBeResolved = false
+		attributes {
+			attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11)
+		}
+	}
+}
+
+artifacts {
+	classesOnlyElements(compileJava.destinationDir)
+}
+
+sourceCompatibility = '11'
+
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
+dependencies {
+	constraints {
+		management("org.opensaml:opensaml-core:4.+")
+		management("org.opensaml:opensaml-saml-api:4.+")
+		management("org.opensaml:opensaml-saml-impl:4.+")
+	}
+
+	compile project(':saml2-service-provider-core')
+
+	compile("org.opensaml:opensaml-core")
+	compile("org.opensaml:opensaml-saml-api")
+	compile("org.opensaml:opensaml-saml-impl")
+
+	provided 'javax.servlet:javax.servlet-api'
+
+	testCompile 'com.squareup.okhttp3:mockwebserver'
+	testCompile project(path : ':saml2-service-provider-core', configuration : 'tests')
+}

+ 770 - 0
saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java

@@ -0,0 +1,770 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import javax.annotation.Nonnull;
+import javax.xml.namespace.QName;
+
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSBoolean;
+import org.opensaml.core.xml.schema.XSBooleanValue;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.XSInteger;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.core.xml.schema.XSURI;
+import org.opensaml.saml.common.assertion.ValidationContext;
+import org.opensaml.saml.common.assertion.ValidationResult;
+import org.opensaml.saml.saml2.assertion.ConditionValidator;
+import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
+import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
+import org.opensaml.saml.saml2.assertion.StatementValidator;
+import org.opensaml.saml.saml2.assertion.SubjectConfirmationValidator;
+import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator;
+import org.opensaml.saml.saml2.assertion.impl.BearerSubjectConfirmationValidator;
+import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionValidator;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.Condition;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.OneTimeUse;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
+import org.opensaml.saml.saml2.encryption.Decrypter;
+import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
+import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
+import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.log.LogMessage;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.OpenSamlInitializationService;
+import org.springframework.security.saml2.core.Saml2Error;
+import org.springframework.security.saml2.core.Saml2ErrorCodes;
+import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Implementation of {@link AuthenticationProvider} for SAML authentications when
+ * receiving a {@code Response} object containing an {@code Assertion}. This
+ * implementation uses the {@code OpenSAML 4} library.
+ *
+ * <p>
+ * The {@link OpenSaml4AuthenticationProvider} supports {@link Saml2AuthenticationToken}
+ * objects that contain a SAML response in its decoded XML format
+ * {@link Saml2AuthenticationToken#getSaml2Response()} along with the information about
+ * the asserting party, the identity provider (IDP), as well as the relying party, the
+ * service provider (SP, this application).
+ * </p>
+ * <p>
+ * The {@link Saml2AuthenticationToken} will be processed into a SAML Response object. The
+ * SAML response object can be signed. If the Response is signed, a signature will not be
+ * required on the assertion.
+ * </p>
+ * <p>
+ * While a response object can contain a list of assertion, this provider will only
+ * leverage the first valid assertion for the purpose of authentication. Assertions that
+ * do not pass validation will be ignored. If no valid assertions are found a
+ * {@link Saml2AuthenticationException} is thrown.
+ * </p>
+ * <p>
+ * This provider supports two types of encrypted SAML elements
+ * <ul>
+ * <li><a href=
+ * "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=17">EncryptedAssertion</a></li>
+ * <li><a href=
+ * "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=14">EncryptedID</a></li>
+ * </ul>
+ * If the assertion is encrypted, then signature validation on the assertion is no longer
+ * required.
+ * </p>
+ * <p>
+ * This provider does not perform an X509 certificate validation on the configured
+ * asserting party, IDP, verification certificates.
+ * </p>
+ *
+ * @author Josh Cummings
+ * @since 5.5
+ * @see <a href=
+ * "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38">SAML 2
+ * StatusResponse</a>
+ * @see <a href="https://wiki.shibboleth.net/confluence/display/OS30/Home">OpenSAML 3</a>
+ */
+public final class OpenSaml4AuthenticationProvider implements AuthenticationProvider {
+
+	static {
+		OpenSamlInitializationService.initialize();
+	}
+
+	private final Log logger = LogFactory.getLog(this.getClass());
+
+	private final ResponseUnmarshaller responseUnmarshaller;
+
+	private final ParserPool parserPool;
+
+	private final Converter<ResponseToken, Saml2ResponseValidatorResult> responseSignatureValidator = createDefaultResponseSignatureValidator();
+
+	private Consumer<ResponseToken> responseElementsDecrypter = createDefaultResponseElementsDecrypter();
+
+	private final Converter<ResponseToken, Saml2ResponseValidatorResult> responseValidator = createDefaultResponseValidator();
+
+	private final Converter<AssertionToken, Saml2ResponseValidatorResult> assertionSignatureValidator = createDefaultAssertionSignatureValidator();
+
+	private Consumer<AssertionToken> assertionElementsDecrypter = createDefaultAssertionElementsDecrypter();
+
+	private Converter<AssertionToken, Saml2ResponseValidatorResult> assertionValidator = createDefaultAssertionValidator();
+
+	private Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter = createDefaultResponseAuthenticationConverter();
+
+	/**
+	 * Creates an {@link OpenSaml4AuthenticationProvider}
+	 */
+	public OpenSaml4AuthenticationProvider() {
+		XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
+		this.responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory()
+				.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME);
+		this.parserPool = registry.getParserPool();
+	}
+
+	/**
+	 * Set the {@link Consumer} strategy to use for decrypting elements of a validated
+	 * {@link Response}. The default strategy decrypts all {@link EncryptedAssertion}s
+	 * using OpenSAML's {@link Decrypter}, adding the results to
+	 * {@link Response#getAssertions()}.
+	 *
+	 * You can use this method to configure the {@link Decrypter} instance like so:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	provider.setResponseElementsDecrypter((responseToken) -> {
+	 *	    DecrypterParameters parameters = new DecrypterParameters();
+	 *	    // ... set parameters as needed
+	 *	    Decrypter decrypter = new Decrypter(parameters);
+	 *		Response response = responseToken.getResponse();
+	 *  	EncryptedAssertion encrypted = response.getEncryptedAssertions().get(0);
+	 *  	try {
+	 *  		Assertion assertion = decrypter.decrypt(encrypted);
+	 *  		response.getAssertions().add(assertion);
+	 *  	} catch (Exception e) {
+	 *  	 	throw new Saml2AuthenticationException(...);
+	 *  	}
+	 *	});
+	 * </pre>
+	 *
+	 * Or, in the event that you have your own custom decryption interface, the same
+	 * pattern applies:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	Converter&lt;EncryptedAssertion, Assertion&gt; myService = ...
+	 *	provider.setResponseDecrypter((responseToken) -> {
+	 *	   Response response = responseToken.getResponse();
+	 *	   response.getEncryptedAssertions().stream()
+	 *	   		.map(service::decrypt).forEach(response.getAssertions()::add);
+	 *	});
+	 * </pre>
+	 *
+	 * This is valuable when using an external service to perform the decryption.
+	 * @param responseElementsDecrypter the {@link Consumer} for decrypting response
+	 * elements
+	 * @since 5.5
+	 */
+	public void setResponseElementsDecrypter(Consumer<ResponseToken> responseElementsDecrypter) {
+		Assert.notNull(responseElementsDecrypter, "responseElementsDecrypter cannot be null");
+		this.responseElementsDecrypter = responseElementsDecrypter;
+	}
+
+	/**
+	 * Set the {@link Converter} to use for validating each {@link Assertion} in the SAML
+	 * 2.0 Response.
+	 *
+	 * You can still invoke the default validator by delgating to
+	 * {@link #createAssertionValidator}, like so:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *  provider.setAssertionValidator(assertionToken -> {
+	 *		Saml2ResponseValidatorResult result = createDefaultAssertionValidator()
+	 *			.convert(assertionToken)
+	 *		return result.concat(myCustomValidator.convert(assertionToken));
+	 *  });
+	 * </pre>
+	 *
+	 * You can also use this method to configure the provider to use a different
+	 * {@link ValidationContext} from the default, like so:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	provider.setAssertionValidator(
+	 *		createDefaultAssertionValidator(assertionToken -> {
+	 *			Map&lt;String, Object&gt; params = new HashMap&lt;&gt;();
+	 *			params.put(CLOCK_SKEW, 2 * 60 * 1000);
+	 *			// other parameters
+	 *			return new ValidationContext(params);
+	 *		}));
+	 * </pre>
+	 *
+	 * Consider taking a look at {@link #createValidationContext} to see how it constructs
+	 * a {@link ValidationContext}.
+	 *
+	 * It is not necessary to delegate to the default validator. You can safely replace it
+	 * entirely with your own. Note that signature verification is performed as a separate
+	 * step from this validator.
+	 * @param assertionValidator the validator to use
+	 * @since 5.4
+	 */
+	public void setAssertionValidator(Converter<AssertionToken, Saml2ResponseValidatorResult> assertionValidator) {
+		Assert.notNull(assertionValidator, "assertionValidator cannot be null");
+		this.assertionValidator = assertionValidator;
+	}
+
+	/**
+	 * Set the {@link Consumer} strategy to use for decrypting elements of a validated
+	 * {@link Assertion}.
+	 *
+	 * You can use this method to configure the {@link Decrypter} used like so:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	provider.setResponseDecrypter((assertionToken) -> {
+	 *	    DecrypterParameters parameters = new DecrypterParameters();
+	 *	    // ... set parameters as needed
+	 *	    Decrypter decrypter = new Decrypter(parameters);
+	 *		Assertion assertion = assertionToken.getAssertion();
+	 *  	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
+	 *  	try {
+	 *  		NameID name = decrypter.decrypt(encrypted);
+	 *  		assertion.getSubject().setNameID(name);
+	 *  	} catch (Exception e) {
+	 *  	 	throw new Saml2AuthenticationException(...);
+	 *  	}
+	 *	});
+	 * </pre>
+	 *
+	 * Or, in the event that you have your own custom interface, the same pattern applies:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 *	MyDecryptionService myService = ...
+	 *	provider.setResponseDecrypter((responseToken) -> {
+	 *	   	Assertion assertion = assertionToken.getAssertion();
+	 *	   	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
+	 *		NameID name = myService.decrypt(encrypted);
+	 *		assertion.getSubject().setNameID(name);
+	 *	});
+	 * </pre>
+	 * @param assertionDecrypter the {@link Consumer} for decrypting assertion elements
+	 * @since 5.5
+	 */
+	public void setAssertionElementsDecrypter(Consumer<AssertionToken> assertionDecrypter) {
+		Assert.notNull(assertionDecrypter, "assertionDecrypter cannot be null");
+		this.assertionElementsDecrypter = assertionDecrypter;
+	}
+
+	/**
+	 * Set the {@link Converter} to use for converting a validated {@link Response} into
+	 * an {@link AbstractAuthenticationToken}.
+	 *
+	 * You can delegate to the default behavior by calling
+	 * {@link #createDefaultResponseAuthenticationConverter()} like so:
+	 *
+	 * <pre>
+	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+	 * 	Converter&lt;ResponseToken, Saml2Authentication&gt; authenticationConverter =
+	 * 			createDefaultResponseAuthenticationConverter();
+	 *	provider.setResponseAuthenticationConverter(responseToken -> {
+	 *		Saml2Authentication authentication = authenticationConverter.convert(responseToken);
+	 *		User user = myUserRepository.findByUsername(authentication.getName());
+	 *		return new MyAuthentication(authentication, user);
+	 *	});
+	 * </pre>
+	 * @param responseAuthenticationConverter the {@link Converter} to use
+	 * @since 5.4
+	 */
+	public void setResponseAuthenticationConverter(
+			Converter<ResponseToken, ? extends AbstractAuthenticationToken> responseAuthenticationConverter) {
+		Assert.notNull(responseAuthenticationConverter, "responseAuthenticationConverter cannot be null");
+		this.responseAuthenticationConverter = responseAuthenticationConverter;
+	}
+
+	/**
+	 * Construct a default strategy for validating each SAML 2.0 Assertion and associated
+	 * {@link Authentication} token
+	 * @return the default assertion validator strategy
+	 */
+	public static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator() {
+
+		return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION,
+				(assertionToken) -> SAML20AssertionValidators.attributeValidator,
+				(assertionToken) -> createValidationContext(assertionToken, (params) -> params
+						.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5).toMillis())));
+	}
+
+	/**
+	 * Construct a default strategy for validating each SAML 2.0 Assertion and associated
+	 * {@link Authentication} token
+	 * @param contextConverter the conversion strategy to use to generate a
+	 * {@link ValidationContext} for each assertion being validated
+	 * @return the default assertion validator strategy
+	 */
+	public static Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionValidator(
+			Converter<AssertionToken, ValidationContext> contextConverter) {
+
+		return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION,
+				(assertionToken) -> SAML20AssertionValidators.attributeValidator, contextConverter);
+	}
+
+	/**
+	 * Construct a default strategy for converting a SAML 2.0 Response and
+	 * {@link Authentication} token into a {@link Saml2Authentication}
+	 * @return the default response authentication converter strategy
+	 */
+	public static Converter<ResponseToken, Saml2Authentication> createDefaultResponseAuthenticationConverter() {
+		return (responseToken) -> {
+			Response response = responseToken.response;
+			Saml2AuthenticationToken token = responseToken.token;
+			Assertion assertion = CollectionUtils.firstElement(response.getAssertions());
+			String username = assertion.getSubject().getNameID().getValue();
+			Map<String, List<Object>> attributes = getAssertionAttributes(assertion);
+			return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal(username, attributes),
+					token.getSaml2Response(), AuthorityUtils.createAuthorityList("ROLE_USER"));
+		};
+	}
+
+	/**
+	 * @param authentication the authentication request object, must be of type
+	 * {@link Saml2AuthenticationToken}
+	 * @return {@link Saml2Authentication} if the assertion is valid
+	 * @throws AuthenticationException if a validation exception occurs
+	 */
+	@Override
+	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+		try {
+			Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication;
+			String serializedResponse = token.getSaml2Response();
+			Response response = parse(serializedResponse);
+			process(token, response);
+			return this.responseAuthenticationConverter.convert(new ResponseToken(response, token));
+		}
+		catch (Saml2AuthenticationException ex) {
+			throw ex;
+		}
+		catch (Exception ex) {
+			throw createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex);
+		}
+	}
+
+	@Override
+	public boolean supports(Class<?> authentication) {
+		return authentication != null && Saml2AuthenticationToken.class.isAssignableFrom(authentication);
+	}
+
+	private Response parse(String response) throws Saml2Exception, Saml2AuthenticationException {
+		try {
+			Document document = this.parserPool
+					.parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8)));
+			Element element = document.getDocumentElement();
+			return (Response) this.responseUnmarshaller.unmarshall(element);
+		}
+		catch (Exception ex) {
+			throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex);
+		}
+	}
+
+	private void process(Saml2AuthenticationToken token, Response response) {
+		String issuer = response.getIssuer().getValue();
+		this.logger.debug(LogMessage.format("Processing SAML response from %s", issuer));
+		boolean responseSigned = response.isSigned();
+
+		ResponseToken responseToken = new ResponseToken(response, token);
+		Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken);
+		if (responseSigned) {
+			this.responseElementsDecrypter.accept(responseToken);
+		}
+		result = result.concat(this.responseValidator.convert(responseToken));
+		boolean allAssertionsSigned = true;
+		for (Assertion assertion : response.getAssertions()) {
+			AssertionToken assertionToken = new AssertionToken(assertion, token);
+			result = result.concat(this.assertionSignatureValidator.convert(assertionToken));
+			allAssertionsSigned = allAssertionsSigned && assertion.isSigned();
+			if (responseSigned || assertion.isSigned()) {
+				this.assertionElementsDecrypter.accept(new AssertionToken(assertion, token));
+			}
+			result = result.concat(this.assertionValidator.convert(assertionToken));
+		}
+		if (!responseSigned && !allAssertionsSigned) {
+			String description = "Either the response or one of the assertions is unsigned. "
+					+ "Please either sign the response or all of the assertions.";
+			throw createAuthenticationException(Saml2ErrorCodes.INVALID_SIGNATURE, description, null);
+		}
+		Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions());
+		if (!hasName(firstAssertion)) {
+			Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
+					"Assertion [" + firstAssertion.getID() + "] is missing a subject");
+			result = result.concat(error);
+		}
+
+		if (result.hasErrors()) {
+			Collection<Saml2Error> errors = result.getErrors();
+			if (this.logger.isTraceEnabled()) {
+				this.logger.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID()
+						+ "]: " + errors);
+			}
+			else if (this.logger.isDebugEnabled()) {
+				this.logger.debug(
+						"Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]");
+			}
+			Saml2Error first = errors.iterator().next();
+			throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null);
+		}
+		else {
+			if (this.logger.isDebugEnabled()) {
+				this.logger.debug("Successfully processed SAML Response [" + response.getID() + "]");
+			}
+		}
+	}
+
+	private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseSignatureValidator() {
+		return (responseToken) -> {
+			Response response = responseToken.getResponse();
+			RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration();
+			if (response.isSigned()) {
+				return OpenSamlVerificationUtils.verifySignature(response, registration).post(response.getSignature());
+			}
+			return Saml2ResponseValidatorResult.success();
+		};
+	}
+
+	private Consumer<ResponseToken> createDefaultResponseElementsDecrypter() {
+		return (responseToken) -> {
+			Response response = responseToken.getResponse();
+			RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration();
+			try {
+				OpenSamlDecryptionUtils.decryptResponseElements(response, registration);
+			}
+			catch (Exception ex) {
+				throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
+			}
+		};
+	}
+
+	private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseValidator() {
+		return (responseToken) -> {
+			Response response = responseToken.getResponse();
+			Saml2AuthenticationToken token = responseToken.getToken();
+			Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success();
+			String issuer = response.getIssuer().getValue();
+			String destination = response.getDestination();
+			String location = token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation();
+			if (StringUtils.hasText(destination) && !destination.equals(location)) {
+				String message = "Invalid destination [" + destination + "] for SAML response [" + response.getID()
+						+ "]";
+				result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, message));
+			}
+			String assertingPartyEntityId = token.getRelyingPartyRegistration().getAssertingPartyDetails()
+					.getEntityId();
+			if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) {
+				String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID());
+				result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, message));
+			}
+			if (response.getAssertions().isEmpty()) {
+				throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA,
+						"No assertions found in response.", null);
+			}
+			return result;
+		};
+	}
+
+	private Converter<AssertionToken, Saml2ResponseValidatorResult> createDefaultAssertionSignatureValidator() {
+		return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, (assertionToken) -> {
+			RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration();
+			SignatureTrustEngine engine = OpenSamlVerificationUtils.trustEngine(registration);
+			return SAML20AssertionValidators.createSignatureValidator(engine);
+		}, (assertionToken) -> new ValidationContext(
+				Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false)));
+	}
+
+	private Consumer<AssertionToken> createDefaultAssertionElementsDecrypter() {
+		return (assertionToken) -> {
+			Assertion assertion = assertionToken.getAssertion();
+			RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration();
+			try {
+				OpenSamlDecryptionUtils.decryptAssertionElements(assertion, registration);
+			}
+			catch (Exception ex) {
+				throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex);
+			}
+		};
+	}
+
+	private boolean hasName(Assertion assertion) {
+		if (assertion == null) {
+			return false;
+		}
+		if (assertion.getSubject() == null) {
+			return false;
+		}
+		if (assertion.getSubject().getNameID() == null) {
+			return false;
+		}
+		return assertion.getSubject().getNameID().getValue() != null;
+	}
+
+	private static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
+		Map<String, List<Object>> attributeMap = new LinkedHashMap<>();
+		for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
+			for (Attribute attribute : attributeStatement.getAttributes()) {
+				List<Object> attributeValues = new ArrayList<>();
+				for (XMLObject xmlObject : attribute.getAttributeValues()) {
+					Object attributeValue = getXmlObjectValue(xmlObject);
+					if (attributeValue != null) {
+						attributeValues.add(attributeValue);
+					}
+				}
+				attributeMap.put(attribute.getName(), attributeValues);
+			}
+		}
+		return attributeMap;
+	}
+
+	private static Object getXmlObjectValue(XMLObject xmlObject) {
+		if (xmlObject instanceof XSAny) {
+			return ((XSAny) xmlObject).getTextContent();
+		}
+		if (xmlObject instanceof XSString) {
+			return ((XSString) xmlObject).getValue();
+		}
+		if (xmlObject instanceof XSInteger) {
+			return ((XSInteger) xmlObject).getValue();
+		}
+		if (xmlObject instanceof XSURI) {
+			return ((XSURI) xmlObject).getURI();
+		}
+		if (xmlObject instanceof XSBoolean) {
+			XSBooleanValue xsBooleanValue = ((XSBoolean) xmlObject).getValue();
+			return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null;
+		}
+		if (xmlObject instanceof XSDateTime) {
+			return ((XSDateTime) xmlObject).getValue();
+		}
+		return null;
+	}
+
+	private static Saml2AuthenticationException createAuthenticationException(String code, String message,
+			Exception cause) {
+		return new Saml2AuthenticationException(new Saml2Error(code, message), cause);
+	}
+
+	private static Converter<AssertionToken, Saml2ResponseValidatorResult> createAssertionValidator(String errorCode,
+			Converter<AssertionToken, SAML20AssertionValidator> validatorConverter,
+			Converter<AssertionToken, ValidationContext> contextConverter) {
+
+		return (assertionToken) -> {
+			Assertion assertion = assertionToken.assertion;
+			SAML20AssertionValidator validator = validatorConverter.convert(assertionToken);
+			ValidationContext context = contextConverter.convert(assertionToken);
+			try {
+				ValidationResult result = validator.validate(assertion, context);
+				if (result == ValidationResult.VALID) {
+					return Saml2ResponseValidatorResult.success();
+				}
+			}
+			catch (Exception ex) {
+				String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(),
+						((Response) assertion.getParent()).getID(), ex.getMessage());
+				return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message));
+			}
+			String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(),
+					((Response) assertion.getParent()).getID(), context.getValidationFailureMessage());
+			return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message));
+		};
+	}
+
+	private static ValidationContext createValidationContext(AssertionToken assertionToken,
+			Consumer<Map<String, Object>> paramsConsumer) {
+		String audience = assertionToken.token.getRelyingPartyRegistration().getEntityId();
+		String recipient = assertionToken.token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation();
+		Map<String, Object> params = new HashMap<>();
+		params.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, Collections.singleton(audience));
+		params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton(recipient));
+		paramsConsumer.accept(params);
+		return new ValidationContext(params);
+	}
+
+	private static class SAML20AssertionValidators {
+
+		private static final Collection<ConditionValidator> conditions = new ArrayList<>();
+
+		private static final Collection<SubjectConfirmationValidator> subjects = new ArrayList<>();
+
+		private static final Collection<StatementValidator> statements = new ArrayList<>();
+
+		private static final SignaturePrevalidator validator = new SAMLSignatureProfileValidator();
+
+		static {
+			conditions.add(new AudienceRestrictionConditionValidator());
+			conditions.add(new DelegationRestrictionConditionValidator());
+			conditions.add(new ConditionValidator() {
+				@Nonnull
+				@Override
+				public QName getServicedCondition() {
+					return OneTimeUse.DEFAULT_ELEMENT_NAME;
+				}
+
+				@Nonnull
+				@Override
+				public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) {
+					// applications should validate their own OneTimeUse conditions
+					return ValidationResult.VALID;
+				}
+			});
+			subjects.add(new BearerSubjectConfirmationValidator() {
+				@Override
+				protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion,
+						ValidationContext context, boolean required) {
+					// applications should validate their own addresses - gh-7514
+					return ValidationResult.VALID;
+				}
+
+				@Override
+				protected ValidationResult validateInResponseTo(SubjectConfirmation confirmation, Assertion assertion,
+						ValidationContext context, boolean required) {
+					// applications should validate their own in response to
+					return ValidationResult.VALID;
+				}
+			});
+		}
+
+		private static final SAML20AssertionValidator attributeValidator = new SAML20AssertionValidator(conditions,
+				subjects, statements, null, null, null) {
+			@Nonnull
+			@Override
+			protected ValidationResult validateSignature(Assertion token, ValidationContext context) {
+				return ValidationResult.VALID;
+			}
+		};
+
+		static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine engine) {
+			return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null, engine,
+					validator) {
+				@Nonnull
+				@Override
+				protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) {
+					return ValidationResult.VALID;
+				}
+
+				@Nonnull
+				@Override
+				protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) {
+					return ValidationResult.VALID;
+				}
+
+				@Nonnull
+				@Override
+				protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) {
+					return ValidationResult.VALID;
+				}
+			};
+
+		}
+
+	}
+
+	/**
+	 * A tuple containing an OpenSAML {@link Response} and its associated authentication
+	 * token.
+	 *
+	 * @since 5.4
+	 */
+	public static class ResponseToken {
+
+		private final Saml2AuthenticationToken token;
+
+		private final Response response;
+
+		ResponseToken(Response response, Saml2AuthenticationToken token) {
+			this.token = token;
+			this.response = response;
+		}
+
+		public Response getResponse() {
+			return this.response;
+		}
+
+		public Saml2AuthenticationToken getToken() {
+			return this.token;
+		}
+
+	}
+
+	/**
+	 * A tuple containing an OpenSAML {@link Assertion} and its associated authentication
+	 * token.
+	 *
+	 * @since 5.4
+	 */
+	public static class AssertionToken {
+
+		private final Saml2AuthenticationToken token;
+
+		private final Assertion assertion;
+
+		AssertionToken(Assertion assertion, Saml2AuthenticationToken token) {
+			this.token = token;
+			this.assertion = assertion;
+		}
+
+		public Assertion getAssertion() {
+			return this.assertion;
+		}
+
+		public Saml2AuthenticationToken getToken() {
+			return this.token;
+		}
+
+	}
+
+}

+ 180 - 0
saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java

@@ -0,0 +1,180 @@
+/*
+ * 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.authentication;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Map;
+import java.util.UUID;
+
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
+import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.saml2.core.OpenSamlInitializationService;
+import org.springframework.security.saml2.provider.service.authentication.OpenSamlSigningUtils.QueryParametersPartial;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link Saml2AuthenticationRequestFactory} that generates, signs, and serializes a
+ * SAML 2.0 AuthnRequest using OpenSAML 4
+ *
+ * @author Josh Cummings
+ * @since 5.5
+ */
+public final class OpenSaml4AuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
+
+	static {
+		OpenSamlInitializationService.initialize();
+	}
+
+	private final AuthnRequestBuilder authnRequestBuilder;
+
+	private final IssuerBuilder issuerBuilder;
+
+	private Clock clock = Clock.systemUTC();
+
+	private Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter;
+
+	/**
+	 * Creates an {@link OpenSaml4AuthenticationRequestFactory}
+	 */
+	public OpenSaml4AuthenticationRequestFactory() {
+		this.authenticationRequestContextConverter = this::createAuthnRequest;
+		XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
+		this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory()
+				.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
+		this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	@Deprecated
+	public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
+		RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("noId")
+				.assertionConsumerServiceBinding(Saml2MessageBinding.POST)
+				.assertionConsumerServiceLocation(request.getAssertionConsumerServiceUrl())
+				.entityId(request.getIssuer()).remoteIdpEntityId("noIssuer").idpWebSsoUrl("noUrl")
+				.credentials((credentials) -> credentials.addAll(request.getCredentials())).build();
+		Saml2AuthenticationRequestContext context = Saml2AuthenticationRequestContext.builder()
+				.relyingPartyRegistration(registration).issuer(request.getIssuer())
+				.assertionConsumerServiceUrl(request.getAssertionConsumerServiceUrl()).build();
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		return OpenSamlSigningUtils.serialize(OpenSamlSigningUtils.sign(authnRequest, registration));
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		RelyingPartyRegistration registration = context.getRelyingPartyRegistration();
+		if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
+			OpenSamlSigningUtils.sign(authnRequest, registration);
+		}
+		String xml = OpenSamlSigningUtils.serialize(authnRequest);
+		return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
+				.samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build();
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
+			Saml2AuthenticationRequestContext context) {
+		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
+		RelyingPartyRegistration registration = context.getRelyingPartyRegistration();
+		String xml = OpenSamlSigningUtils.serialize(authnRequest);
+		Saml2RedirectAuthenticationRequest.Builder result = Saml2RedirectAuthenticationRequest
+				.withAuthenticationRequestContext(context);
+		String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
+		result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState());
+		if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
+			QueryParametersPartial partial = OpenSamlSigningUtils.sign(registration).param("SAMLRequest",
+					deflatedAndEncoded);
+			if (StringUtils.hasText(context.getRelayState())) {
+				partial.param("RelayState", context.getRelayState());
+			}
+			Map<String, String> parameters = partial.parameters();
+			return result.sigAlg(parameters.get("SigAlg")).signature(parameters.get("Signature")).build();
+		}
+		return result.build();
+	}
+
+	private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) {
+		String issuer = context.getIssuer();
+		String destination = context.getDestination();
+		String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();
+		String protocolBinding = context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
+		AuthnRequest auth = this.authnRequestBuilder.buildObject();
+		if (auth.getID() == null) {
+			auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
+		}
+		if (auth.getIssueInstant() == null) {
+			auth.setIssueInstant(Instant.now(this.clock));
+		}
+		if (auth.isForceAuthn() == null) {
+			auth.setForceAuthn(Boolean.FALSE);
+		}
+		if (auth.isPassive() == null) {
+			auth.setIsPassive(Boolean.FALSE);
+		}
+		if (auth.getProtocolBinding() == null) {
+			auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
+		}
+		auth.setProtocolBinding(protocolBinding);
+		Issuer iss = this.issuerBuilder.buildObject();
+		iss.setValue(issuer);
+		auth.setIssuer(iss);
+		auth.setDestination(destination);
+		auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
+		return auth;
+	}
+
+	/**
+	 * Set the strategy for building an {@link AuthnRequest} from a given context
+	 * @param authenticationRequestContextConverter the conversion strategy to use
+	 */
+	public void setAuthenticationRequestContextConverter(
+			Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter) {
+		Assert.notNull(authenticationRequestContextConverter, "authenticationRequestContextConverter cannot be null");
+		this.authenticationRequestContextConverter = authenticationRequestContextConverter;
+	}
+
+	/**
+	 * Use this {@link Clock} with {@link Instant#now()} for generating timestamps
+	 * @param clock the {@link Clock} to use
+	 */
+	public void setClock(Clock clock) {
+		Assert.notNull(clock, "clock cannot be null");
+		this.clock = clock;
+	}
+
+}

+ 661 - 0
saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java

@@ -0,0 +1,661 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import javax.xml.namespace.QName;
+
+import net.shibboleth.utilities.java.support.xml.SerializeSupport;
+import org.junit.Test;
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.io.Marshaller;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.schema.XSDateTime;
+import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder;
+import org.opensaml.saml.common.assertion.ValidationContext;
+import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.AttributeValue;
+import org.opensaml.saml.saml2.core.Conditions;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.EncryptedAttribute;
+import org.opensaml.saml.saml2.core.EncryptedID;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.OneTimeUse;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.SubjectConfirmation;
+import org.opensaml.saml.saml2.core.SubjectConfirmationData;
+import org.opensaml.saml.saml2.core.impl.AttributeBuilder;
+import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder;
+import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder;
+import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
+import org.opensaml.xmlsec.encryption.impl.EncryptedDataBuilder;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.w3c.dom.Element;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.Saml2Error;
+import org.springframework.security.saml2.core.Saml2ErrorCodes;
+import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
+import org.springframework.security.saml2.core.TestSaml2X509Credentials;
+import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
+import org.springframework.util.StringUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link OpenSaml4AuthenticationProvider}
+ *
+ * @author Filip Hanik
+ * @author Josh Cummings
+ */
+public class OpenSaml4AuthenticationProviderTests {
+
+	private static String DESTINATION = "https://localhost/login/saml2/sso/idp-alias";
+
+	private static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias";
+
+	private static String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp";
+
+	private OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+
+	private Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("name",
+			Collections.emptyMap());
+
+	private Saml2Authentication authentication = new Saml2Authentication(this.principal, "response",
+			Collections.emptyList());
+
+	@Test
+	public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() {
+		assertThat(this.provider.supports(Saml2AuthenticationToken.class))
+				.withFailMessage(
+						OpenSaml4AuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class)
+				.isTrue();
+	}
+
+	@Test
+	public void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() {
+		assertThat(!this.provider.supports(Authentication.class))
+				.withFailMessage(OpenSaml4AuthenticationProvider.class + "should not support " + Authentication.class)
+				.isTrue();
+	}
+
+	@Test
+	public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() {
+		Assertion assertion = (Assertion) XMLObjectProviderRegistrySupport.getBuilderFactory()
+				.getBuilder(Assertion.DEFAULT_ELEMENT_NAME).buildObject(Assertion.DEFAULT_ELEMENT_NAME);
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(
+						new Saml2AuthenticationToken(verifying(registration()).build(), serialize(assertion))))
+				.satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA));
+	}
+
+	@Test
+	public void authenticateWhenXmlErrorThenThrowAuthenticationException() {
+		Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), "invalid xml");
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA));
+	}
+
+	@Test
+	public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() {
+		Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion());
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.INVALID_DESTINATION));
+	}
+
+	@Test
+	public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() {
+		Saml2AuthenticationToken token = token();
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response."));
+	}
+
+	@Test
+	public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() {
+		Response response = response();
+		response.getAssertions().add(assertion());
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE));
+	}
+
+	@Test
+	public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() {
+		Response response = response();
+		Assertion assertion = assertion();
+		assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData()
+				.setNotOnOrAfter(Instant.now().minus(Duration.ofDays(3)));
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION));
+	}
+
+	@Test
+	public void authenticateWhenMissingSubjectThenThrowAuthenticationException() {
+		Response response = response();
+		Assertion assertion = assertion();
+		assertion.setSubject(null);
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND));
+	}
+
+	@Test
+	public void authenticateWhenUsernameMissingThenThrowAuthenticationException() {
+		Response response = response();
+		Assertion assertion = assertion();
+		assertion.getSubject().getNameID().setValue(null);
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND));
+	}
+
+	@Test
+	public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() {
+		Response response = response();
+		Assertion assertion = assertion();
+		assertion.getSubject().getSubjectConfirmations()
+				.forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10"));
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		this.provider.authenticate(token);
+	}
+
+	@Test
+	public void authenticateWhenAssertionContainsAttributesThenItSucceeds() {
+		Response response = response();
+		Assertion assertion = assertion();
+		List<AttributeStatement> attributes = attributeStatements();
+		assertion.getAttributeStatements().addAll(attributes);
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		Authentication authentication = this.provider.authenticate(token);
+		Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
+		Map<String, Object> expected = new LinkedHashMap<>();
+		expected.put("email", Arrays.asList("john.doe@example.com", "doe.john@example.com"));
+		expected.put("name", Collections.singletonList("John Doe"));
+		expected.put("age", Collections.singletonList(21));
+		expected.put("website", Collections.singletonList("https://johndoe.com/"));
+		expected.put("registered", Collections.singletonList(true));
+		Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z");
+		expected.put("registeredDate", Collections.singletonList(registeredDate));
+		assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe");
+		assertThat(principal.getAttributes()).isEqualTo(expected);
+	}
+
+	@Test
+	public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() {
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		response.getEncryptedAssertions().add(encryptedAssertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, decrypting(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE));
+	}
+
+	@Test
+	public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() {
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
+				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		response.getEncryptedAssertions().add(encryptedAssertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, decrypting(verifying(registration())));
+		this.provider.authenticate(token);
+	}
+
+	@Test
+	public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() {
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		response.getEncryptedAssertions().add(encryptedAssertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, decrypting(verifying(registration())));
+		this.provider.authenticate(token);
+	}
+
+	@Test
+	public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() {
+		Response response = response();
+		Assertion assertion = assertion();
+		NameID nameId = assertion.getSubject().getNameID();
+		EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId,
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		assertion.getSubject().setNameID(null);
+		assertion.getSubject().setEncryptedID(encryptedID);
+		response.getAssertions().add(assertion);
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, decrypting(verifying(registration())));
+		this.provider.authenticate(token);
+	}
+
+	@Test
+	public void authenticateWhenEncryptedAttributeThenDecrypts() {
+		Response response = response();
+		Assertion assertion = assertion();
+		EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value",
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME);
+		statement.getEncryptedAttributes().add(attribute);
+		assertion.getAttributeStatements().add(statement);
+		response.getAssertions().add(assertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, decrypting(verifying(registration())));
+		Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token);
+		Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
+		assertThat(principal.getAttribute("name")).containsExactly("value");
+	}
+
+	@Test
+	public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() {
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		response.getEncryptedAssertions().add(encryptedAssertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData"));
+	}
+
+	@Test
+	public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() {
+		Response response = response();
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		response.getEncryptedAssertions().add(encryptedAssertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, registration()
+				.decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential())));
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> this.provider.authenticate(token))
+				.satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData"));
+	}
+
+	@Test
+	public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException {
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
+				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID);
+		EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion,
+				TestSaml2X509Credentials.assertingPartyEncryptingCredential());
+		response.getEncryptedAssertions().add(encryptedAssertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, decrypting(verifying(registration())));
+		Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token);
+		// the following code will throw an exception if authentication isn't serializable
+		ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
+		ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
+		objectOutputStream.writeObject(authentication);
+		objectOutputStream.flush();
+	}
+
+	@Test
+	public void createDefaultAssertionValidatorWhenAssertionThenValidates() {
+		Response response = TestOpenSamlObjects.signedResponseWithOneAssertion();
+		Assertion assertion = response.getAssertions().get(0);
+		OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken(
+				assertion, token());
+		assertThat(
+				OpenSaml4AuthenticationProvider.createDefaultAssertionValidator().convert(assertionToken).hasErrors())
+						.isFalse();
+	}
+
+	@Test
+	public void authenticateWhenDelegatingToDefaultAssertionValidatorThenUses() {
+		OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+		// @formatter:off
+		provider.setAssertionValidator((assertionToken) -> OpenSaml4AuthenticationProvider
+				.createDefaultAssertionValidator((token) -> new ValidationContext())
+				.convert(assertionToken)
+				.concat(new Saml2Error("wrong error", "wrong error"))
+		);
+		// @formatter:on
+		Response response = response();
+		Assertion assertion = assertion();
+		OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME);
+		assertion.getConditions().getConditions().add(oneTimeUse);
+		response.getAssertions().add(assertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				ASSERTING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		// @formatter:off
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class)
+				.satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_ASSERTION));
+		// @formatter:on
+	}
+
+	@Test
+	public void authenticateWhenCustomAssertionValidatorThenUses() {
+		Converter<OpenSaml4AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> validator = mock(
+				Converter.class);
+		OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+		// @formatter:off
+		provider.setAssertionValidator((assertionToken) -> OpenSaml4AuthenticationProvider.createDefaultAssertionValidator()
+				.convert(assertionToken)
+				.concat(validator.convert(assertionToken))
+		);
+		// @formatter:on
+		Response response = response();
+		Assertion assertion = assertion();
+		response.getAssertions().add(assertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				ASSERTING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		given(validator.convert(any(OpenSaml4AuthenticationProvider.AssertionToken.class)))
+				.willReturn(Saml2ResponseValidatorResult.success());
+		provider.authenticate(token);
+		verify(validator).convert(any(OpenSaml4AuthenticationProvider.AssertionToken.class));
+	}
+
+	@Test
+	public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() {
+		OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+		provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success());
+		Response response = response();
+		Assertion assertion = assertion();
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(),
+				RELYING_PARTY_ENTITY_ID); // broken
+		// signature
+		response.getAssertions().add(assertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				ASSERTING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		// @formatter:off
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> provider.authenticate(token))
+				.satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_SIGNATURE));
+		// @formatter:on
+	}
+
+	@Test
+	public void authenticateWhenValidationContextCustomizedThenUsers() {
+		Map<String, Object> parameters = new HashMap<>();
+		parameters.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton("blah"));
+		ValidationContext context = mock(ValidationContext.class);
+		given(context.getStaticParameters()).willReturn(parameters);
+		OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+		provider.setAssertionValidator(
+				OpenSaml4AuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context));
+		Response response = response();
+		Assertion assertion = assertion();
+		response.getAssertions().add(assertion);
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				ASSERTING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		// @formatter:off
+		assertThatExceptionOfType(Saml2AuthenticationException.class)
+				.isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class)
+				.satisfies((error) -> assertThat(error).hasMessageContaining("Invalid assertion"));
+		// @formatter:on
+		verify(context, atLeastOnce()).getStaticParameters();
+	}
+
+	@Test
+	public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception {
+		Response response = response();
+		Assertion assertion = TestOpenSamlObjects.signed(assertion(),
+				TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID,
+				SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		this.provider.authenticate(token);
+	}
+
+	@Test
+	public void setAssertionValidatorWhenNullThenIllegalArgument() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.provider.setAssertionValidator(null));
+		// @formatter:on
+	}
+
+	@Test
+	public void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() {
+		Response response = TestOpenSamlObjects.signedResponseWithOneAssertion();
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		ResponseToken responseToken = new ResponseToken(response, token);
+		Saml2Authentication authentication = OpenSaml4AuthenticationProvider
+				.createDefaultResponseAuthenticationConverter().convert(responseToken);
+		assertThat(authentication.getName()).isEqualTo("test@saml.user");
+	}
+
+	@Test
+	public void authenticateWhenResponseAuthenticationConverterConfiguredThenUses() {
+		Converter<ResponseToken, Saml2Authentication> authenticationConverter = mock(Converter.class);
+		OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+		provider.setResponseAuthenticationConverter(authenticationConverter);
+		Response response = TestOpenSamlObjects.signedResponseWithOneAssertion();
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		provider.authenticate(token);
+		verify(authenticationConverter).convert(any());
+	}
+
+	@Test
+	public void setResponseAuthenticationConverterWhenNullThenIllegalArgument() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.provider.setResponseAuthenticationConverter(null));
+		// @formatter:on
+	}
+
+	@Test
+	public void setResponseElementsDecrypterWhenNullThenIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setResponseElementsDecrypter(null));
+	}
+
+	@Test
+	public void setAssertionElementsDecrypterWhenNullThenIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setAssertionElementsDecrypter(null));
+	}
+
+	@Test
+	public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse() {
+		Response response = response();
+		Assertion assertion = assertion();
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getEncryptedAssertions().add(new EncryptedAssertionBuilder().buildObject());
+		TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		this.provider.setResponseElementsDecrypter((tuple) -> tuple.getResponse().getAssertions().add(assertion));
+		Authentication authentication = this.provider.authenticate(token);
+		assertThat(authentication.getName()).isEqualTo("test@saml.user");
+	}
+
+	@Test
+	public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertion() {
+		Response response = response();
+		Assertion assertion = assertion();
+		EncryptedID id = new EncryptedIDBuilder().buildObject();
+		id.setEncryptedData(new EncryptedDataBuilder().buildObject());
+		assertion.getSubject().setEncryptedID(id);
+		TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.assertingPartySigningCredential(),
+				RELYING_PARTY_ENTITY_ID);
+		response.getAssertions().add(assertion);
+		Saml2AuthenticationToken token = token(response, verifying(registration()));
+		this.provider.setAssertionElementsDecrypter((tuple) -> {
+			NameID name = new NameIDBuilder().buildObject();
+			name.setValue("decrypted name");
+			tuple.getAssertion().getSubject().setNameID(name);
+		});
+		Authentication authentication = this.provider.authenticate(token);
+		assertThat(authentication.getName()).isEqualTo("decrypted name");
+	}
+
+	private <T extends XMLObject> T build(QName qName) {
+		return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
+	}
+
+	private String serialize(XMLObject object) {
+		try {
+			Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object);
+			Element element = marshaller.marshall(object);
+			return SerializeSupport.nodeToString(element);
+		}
+		catch (MarshallingException ex) {
+			throw new Saml2Exception(ex);
+		}
+	}
+
+	private Consumer<Saml2AuthenticationException> errorOf(String errorCode) {
+		return errorOf(errorCode, null);
+	}
+
+	private Consumer<Saml2AuthenticationException> errorOf(String errorCode, String description) {
+		return (ex) -> {
+			assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(errorCode);
+			if (StringUtils.hasText(description)) {
+				assertThat(ex.getSaml2Error().getDescription()).contains(description);
+			}
+		};
+	}
+
+	private Response response() {
+		Response response = TestOpenSamlObjects.response();
+		response.setIssueInstant(Instant.now());
+		return response;
+	}
+
+	private Response response(String destination, String issuerEntityId) {
+		Response response = TestOpenSamlObjects.response(destination, issuerEntityId);
+		response.setIssueInstant(Instant.now());
+		return response;
+	}
+
+	private Assertion assertion() {
+		Assertion assertion = TestOpenSamlObjects.assertion();
+		assertion.setIssueInstant(Instant.now());
+		for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) {
+			SubjectConfirmationData data = confirmation.getSubjectConfirmationData();
+			data.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000)));
+			data.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000)));
+		}
+		Conditions conditions = assertion.getConditions();
+		conditions.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000)));
+		conditions.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000)));
+		return assertion;
+	}
+
+	private List<AttributeStatement> attributeStatements() {
+		List<AttributeStatement> attributeStatements = TestOpenSamlObjects.attributeStatements();
+		AttributeBuilder attributeBuilder = new AttributeBuilder();
+		Attribute registeredDateAttr = attributeBuilder.buildObject();
+		registeredDateAttr.setName("registeredDate");
+		XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
+				XSDateTime.TYPE_NAME);
+		registeredDate.setValue(Instant.parse("1970-01-01T00:00:00Z"));
+		registeredDateAttr.getAttributeValues().add(registeredDate);
+		attributeStatements.iterator().next().getAttributes().add(registeredDateAttr);
+		return attributeStatements;
+	}
+
+	private Saml2AuthenticationToken token() {
+		Response response = response();
+		RelyingPartyRegistration registration = verifying(registration()).build();
+		return new Saml2AuthenticationToken(registration, serialize(response));
+	}
+
+	private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration) {
+		return new Saml2AuthenticationToken(registration.build(), serialize(response));
+	}
+
+	private RelyingPartyRegistration.Builder registration() {
+		return TestRelyingPartyRegistrations.noCredentials().entityId(RELYING_PARTY_ENTITY_ID)
+				.assertionConsumerServiceLocation(DESTINATION)
+				.assertingPartyDetails((party) -> party.entityId(ASSERTING_PARTY_ENTITY_ID));
+	}
+
+	private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) {
+		return builder.assertingPartyDetails((party) -> party
+				.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())));
+	}
+
+	private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) {
+		return builder
+				.decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential()));
+	}
+
+}

+ 274 - 0
saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java

@@ -0,0 +1,274 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.impl.AuthnRequestUnmarshaller;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.Saml2X509Credential;
+import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
+import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link OpenSaml4AuthenticationRequestFactory}
+ */
+public class OpenSaml4AuthenticationRequestFactoryTests {
+
+	private OpenSaml4AuthenticationRequestFactory factory;
+
+	private Saml2AuthenticationRequestContext.Builder contextBuilder;
+
+	private Saml2AuthenticationRequestContext context;
+
+	private RelyingPartyRegistration.Builder relyingPartyRegistrationBuilder;
+
+	private RelyingPartyRegistration relyingPartyRegistration;
+
+	private AuthnRequestUnmarshaller unmarshaller;
+
+	@Before
+	public void setUp() {
+		this.relyingPartyRegistrationBuilder = RelyingPartyRegistration.withRegistrationId("id")
+				.assertionConsumerServiceLocation("template")
+				.providerDetails((c) -> c.webSsoUrl("https://destination/sso"))
+				.providerDetails((c) -> c.entityId("remote-entity-id")).localEntityIdTemplate("local-entity-id")
+				.credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential()));
+		this.relyingPartyRegistration = this.relyingPartyRegistrationBuilder.build();
+		this.contextBuilder = Saml2AuthenticationRequestContext.builder().issuer("https://issuer")
+				.relyingPartyRegistration(this.relyingPartyRegistration)
+				.assertionConsumerServiceUrl("https://issuer/sso");
+		this.context = this.contextBuilder.build();
+		this.factory = new OpenSaml4AuthenticationRequestFactory();
+		this.unmarshaller = (AuthnRequestUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory()
+				.getUnmarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME);
+	}
+
+	@Test
+	public void createAuthenticationRequestWhenInvokingDeprecatedMethodThenReturnsXML() {
+		Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(this.context)
+				.build();
+		String result = this.factory.createAuthenticationRequest(request);
+		assertThat(result.replace("\n", ""))
+				.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2p:AuthnRequest");
+	}
+
+	@Test
+	public void createRedirectAuthenticationRequestWhenUsingContextThenAllValuesAreSet() {
+		this.context = this.contextBuilder.relayState("Relay State Value").build();
+		Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(this.context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getSigAlg()).isNotEmpty();
+		assertThat(result.getSignature()).isNotEmpty();
+		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
+	}
+
+	@Test
+	public void createRedirectAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() {
+		this.context = this.contextBuilder.relayState("Relay State Value")
+				.relyingPartyRegistration(
+						RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration)
+								.providerDetails((c) -> c.signAuthNRequest(false)).build())
+				.build();
+		Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(this.context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getSigAlg()).isNull();
+		assertThat(result.getSignature()).isNull();
+		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
+	}
+
+	@Test
+	public void createRedirectAuthenticationRequestWhenSignRequestThenSignatureIsPresent() {
+		this.context = this.contextBuilder.relayState("Relay State Value")
+				.relyingPartyRegistration(this.relyingPartyRegistration).build();
+		Saml2RedirectAuthenticationRequest request = this.factory.createRedirectAuthenticationRequest(this.context);
+		assertThat(request.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(request.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
+		assertThat(request.getSignature()).isNotNull();
+	}
+
+	@Test
+	public void createRedirectAuthenticationRequestWhenSignRequestThenCredentialIsRequired() {
+		Saml2X509Credential credential = org.springframework.security.saml2.core.TestSaml2X509Credentials
+				.relyingPartyVerifyingCredential();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials()
+				.assertingPartyDetails((party) -> party.verificationX509Credentials((c) -> c.add(credential))).build();
+		this.context = this.contextBuilder.relayState("Relay State Value").relyingPartyRegistration(registration)
+				.build();
+		assertThatExceptionOfType(Saml2Exception.class)
+				.isThrownBy(() -> this.factory.createPostAuthenticationRequest(this.context));
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenNotSignRequestThenNoSignatureIsPresent() {
+		this.context = this.contextBuilder.relayState("Relay State Value")
+				.relyingPartyRegistration(
+						RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration)
+								.providerDetails((c) -> c.signAuthNRequest(false)).build())
+				.build();
+		Saml2PostAuthenticationRequest result = this.factory.createPostAuthenticationRequest(this.context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST);
+		assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()), StandardCharsets.UTF_8))
+				.doesNotContain("ds:Signature");
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenSignRequestThenSignatureIsPresent() {
+		this.context = this.contextBuilder.relayState("Relay State Value")
+				.relyingPartyRegistration(
+						RelyingPartyRegistration.withRelyingPartyRegistration(this.relyingPartyRegistration).build())
+				.build();
+		Saml2PostAuthenticationRequest result = this.factory.createPostAuthenticationRequest(this.context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST);
+		assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()), StandardCharsets.UTF_8))
+				.contains("ds:Signature");
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenSignRequestThenCredentialIsRequired() {
+		Saml2X509Credential credential = org.springframework.security.saml2.core.TestSaml2X509Credentials
+				.relyingPartyVerifyingCredential();
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials()
+				.assertingPartyDetails((party) -> party.verificationX509Credentials((c) -> c.add(credential))).build();
+		this.context = this.contextBuilder.relayState("Relay State Value").relyingPartyRegistration(registration)
+				.build();
+		assertThatExceptionOfType(Saml2Exception.class)
+				.isThrownBy(() -> this.factory.createPostAuthenticationRequest(this.context));
+	}
+
+	@Test
+	public void createAuthenticationRequestWhenDefaultThenReturnsPostBinding() {
+		AuthnRequest authn = getAuthNRequest(Saml2MessageBinding.POST);
+		Assert.assertEquals(SAMLConstants.SAML2_POST_BINDING_URI, authn.getProtocolBinding());
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
+		Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = mock(
+				Converter.class);
+		given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest());
+		this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
+
+		this.factory.createPostAuthenticationRequest(this.context);
+		verify(authenticationRequestContextConverter).convert(this.context);
+	}
+
+	@Test
+	public void createRedirectAuthenticationRequestWhenAuthnRequestConsumerThenUses() {
+		Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = mock(
+				Converter.class);
+		given(authenticationRequestContextConverter.convert(this.context)).willReturn(authnRequest());
+		this.factory.setAuthenticationRequestContextConverter(authenticationRequestContextConverter);
+
+		this.factory.createRedirectAuthenticationRequest(this.context);
+		verify(authenticationRequestContextConverter).convert(this.context);
+	}
+
+	@Test
+	public void setAuthenticationRequestContextConverterWhenNullThenException() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.factory.setAuthenticationRequestContextConverter(null));
+		// @formatter:on
+	}
+
+	@Test
+	public void createPostAuthenticationRequestWhenAssertionConsumerServiceBindingThenUses() {
+		RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationBuilder
+				.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build();
+		Saml2AuthenticationRequestContext context = this.contextBuilder
+				.relyingPartyRegistration(relyingPartyRegistration).build();
+		Saml2PostAuthenticationRequest request = this.factory.createPostAuthenticationRequest(context);
+		String samlRequest = request.getSamlRequest();
+		String inflated = new String(Saml2Utils.samlDecode(samlRequest));
+		assertThat(inflated).contains("ProtocolBinding=\"" + SAMLConstants.SAML2_REDIRECT_BINDING_URI + "\"");
+	}
+
+	@Test
+	public void createRedirectAuthenticationRequestWhenSHA1SignRequestThenSignatureIsPresent() {
+		RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationBuilder
+				.assertingPartyDetails(
+						(a) -> a.signingAlgorithms((algs) -> algs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1)))
+				.build();
+		Saml2AuthenticationRequestContext context = this.contextBuilder.relayState("Relay State Value")
+				.relyingPartyRegistration(relyingPartyRegistration).build();
+		Saml2RedirectAuthenticationRequest result = this.factory.createRedirectAuthenticationRequest(context);
+		assertThat(result.getSamlRequest()).isNotEmpty();
+		assertThat(result.getRelayState()).isEqualTo("Relay State Value");
+		assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
+		assertThat(result.getSignature()).isNotNull();
+		assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
+	}
+
+	private AuthnRequest authnRequest() {
+		AuthnRequest authnRequest = TestOpenSamlObjects.authnRequest();
+		authnRequest.setIssueInstant(Instant.now());
+		return authnRequest;
+	}
+
+	private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) {
+		AbstractSaml2AuthenticationRequest result = (binding == Saml2MessageBinding.REDIRECT)
+				? this.factory.createRedirectAuthenticationRequest(this.context)
+				: this.factory.createPostAuthenticationRequest(this.context);
+		String samlRequest = result.getSamlRequest();
+		assertThat(samlRequest).isNotEmpty();
+		if (result.getBinding() == Saml2MessageBinding.REDIRECT) {
+			samlRequest = Saml2Utils.samlInflate(Saml2Utils.samlDecode(samlRequest));
+		}
+		else {
+			samlRequest = new String(Saml2Utils.samlDecode(samlRequest), StandardCharsets.UTF_8);
+		}
+		try {
+			Document document = XMLObjectProviderRegistrySupport.getParserPool()
+					.parse(new ByteArrayInputStream(samlRequest.getBytes(StandardCharsets.UTF_8)));
+			Element element = document.getDocumentElement();
+			return (AuthnRequest) this.unmarshaller.unmarshall(element);
+		}
+		catch (Exception ex) {
+			throw new Saml2Exception(ex);
+		}
+	}
+
+}

+ 42 - 8
saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle

@@ -1,14 +1,48 @@
 apply plugin: 'io.spring.convention.spring-module'
 
-dependencies {
-	compile project(':spring-security-core')
-	compile project(':spring-security-web')
+build.dependsOn(project(":saml2-service-provider-core").tasks["build"])
+build.dependsOn(project(":saml2-service-provider-opensaml3").tasks["build"])
+build.dependsOn(project(":saml2-service-provider-opensaml4").tasks["build"])
+
+check.dependsOn(project(":saml2-service-provider-core").tasks["check"])
+check.dependsOn(project(":saml2-service-provider-opensaml3").tasks["check"])
+check.dependsOn(project(":saml2-service-provider-opensaml4").tasks["check"])
+
+test.dependsOn(project(":saml2-service-provider-core").tasks["test"])
+test.dependsOn(project(":saml2-service-provider-opensaml3").tasks["test"])
+test.dependsOn(project(":saml2-service-provider-opensaml4").tasks["test"])
+
+clean.dependsOn(project(":saml2-service-provider-core").tasks["clean"])
+clean.dependsOn(project(":saml2-service-provider-opensaml3").tasks["clean"])
+clean.dependsOn(project(":saml2-service-provider-opensaml4").tasks["clean"])
 
-	compile("org.opensaml:opensaml-core")
-	compile("org.opensaml:opensaml-saml-api")
-	compile("org.opensaml:opensaml-saml-impl")
+format.dependsOn(project(":saml2-service-provider-core").tasks["format"])
+format.dependsOn(project(":saml2-service-provider-opensaml3").tasks["format"])
+format.dependsOn(project(":saml2-service-provider-opensaml4").tasks["format"])
 
-	provided 'javax.servlet:javax.servlet-api'
+configurations {
+	core {
+		canBeConsumed = false
+		canBeResolved = true
+	}
+	opensaml3 {
+		canBeConsumed = false
+		canBeResolved = true
+	}
+	opensaml4 {
+		canBeConsumed = false
+		canBeResolved = true
+	}
+}
+
+dependencies {
+	core(project(path: ":saml2-service-provider-core", configuration: 'classesOnlyElements'))
+	opensaml3(project(path: ":saml2-service-provider-opensaml3", configuration: 'classesOnlyElements'))
+	opensaml4(project(path: ":saml2-service-provider-opensaml4", configuration: 'classesOnlyElements'))
+}
 
-	testCompile 'com.squareup.okhttp3:mockwebserver'
+jar {
+	from configurations.core
+	from configurations.opensaml3
+	from configurations.opensaml4
 }

+ 0 - 311
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java

@@ -1,311 +0,0 @@
-/*
- * 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.authentication;
-
-import java.nio.charset.StandardCharsets;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.time.Clock;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
-import net.shibboleth.utilities.java.support.xml.SerializeSupport;
-import org.joda.time.DateTime;
-import org.opensaml.core.config.ConfigurationService;
-import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
-import org.opensaml.core.xml.io.MarshallingException;
-import org.opensaml.saml.common.xml.SAMLConstants;
-import org.opensaml.saml.saml2.core.AuthnRequest;
-import org.opensaml.saml.saml2.core.Issuer;
-import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
-import org.opensaml.saml.saml2.core.impl.AuthnRequestMarshaller;
-import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
-import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver;
-import org.opensaml.security.SecurityException;
-import org.opensaml.security.credential.BasicCredential;
-import org.opensaml.security.credential.Credential;
-import org.opensaml.security.credential.CredentialSupport;
-import org.opensaml.security.credential.UsageType;
-import org.opensaml.xmlsec.SignatureSigningParameters;
-import org.opensaml.xmlsec.SignatureSigningParametersResolver;
-import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion;
-import org.opensaml.xmlsec.crypto.XMLSigningUtil;
-import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
-import org.opensaml.xmlsec.signature.support.SignatureConstants;
-import org.opensaml.xmlsec.signature.support.SignatureSupport;
-import org.w3c.dom.Element;
-
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.security.saml2.Saml2Exception;
-import org.springframework.security.saml2.core.OpenSamlInitializationService;
-import org.springframework.security.saml2.core.Saml2X509Credential;
-import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.Builder;
-import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
-import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-import org.springframework.web.util.UriComponentsBuilder;
-import org.springframework.web.util.UriUtils;
-
-/**
- * @since 5.2
- */
-public class OpenSamlAuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
-
-	static {
-		OpenSamlInitializationService.initialize();
-	}
-
-	private Clock clock = Clock.systemUTC();
-
-	private AuthnRequestMarshaller marshaller;
-
-	private AuthnRequestBuilder authnRequestBuilder;
-
-	private IssuerBuilder issuerBuilder;
-
-	private Converter<Saml2AuthenticationRequestContext, String> protocolBindingResolver = (context) -> {
-		if (context == null) {
-			return SAMLConstants.SAML2_POST_BINDING_URI;
-		}
-		return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
-	};
-
-	private Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter = this::createAuthnRequest;
-
-	/**
-	 * Creates an {@link OpenSamlAuthenticationRequestFactory}
-	 */
-	public OpenSamlAuthenticationRequestFactory() {
-		XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
-		this.marshaller = (AuthnRequestMarshaller) registry.getMarshallerFactory()
-				.getMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME);
-		this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory()
-				.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
-		this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
-	}
-
-	@Override
-	@Deprecated
-	public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
-		AuthnRequest authnRequest = createAuthnRequest(request.getIssuer(), request.getDestination(),
-				request.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(null));
-		for (org.springframework.security.saml2.credentials.Saml2X509Credential credential : request.getCredentials()) {
-			if (credential.isSigningCredential()) {
-				X509Certificate certificate = credential.getCertificate();
-				PrivateKey privateKey = credential.getPrivateKey();
-				BasicCredential cred = CredentialSupport.getSimpleCredential(certificate, privateKey);
-				cred.setEntityId(request.getIssuer());
-				cred.setUsageType(UsageType.SIGNING);
-				SignatureSigningParameters parameters = new SignatureSigningParameters();
-				parameters.setSigningCredential(cred);
-				parameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
-				parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
-				parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
-				return serialize(sign(authnRequest, parameters));
-			}
-		}
-		throw new IllegalArgumentException("No signing credential provided");
-	}
-
-	@Override
-	public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
-		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
-		String xml = context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()
-				? serialize(sign(authnRequest, context.getRelyingPartyRegistration())) : serialize(authnRequest);
-
-		return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
-				.samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build();
-	}
-
-	@Override
-	public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
-			Saml2AuthenticationRequestContext context) {
-		AuthnRequest authnRequest = this.authenticationRequestContextConverter.convert(context);
-		String xml = serialize(authnRequest);
-		Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context);
-		String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
-		result.samlRequest(deflatedAndEncoded).relayState(context.getRelayState());
-		if (context.getRelyingPartyRegistration().getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
-			Map<String, String> parameters = new LinkedHashMap<>();
-			parameters.put("SAMLRequest", deflatedAndEncoded);
-			if (StringUtils.hasText(context.getRelayState())) {
-				parameters.put("RelayState", context.getRelayState());
-			}
-			sign(parameters, context.getRelyingPartyRegistration());
-			return result.sigAlg(parameters.get("SigAlg")).signature(parameters.get("Signature")).build();
-		}
-		return result.build();
-	}
-
-	private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) {
-		return createAuthnRequest(context.getIssuer(), context.getDestination(),
-				context.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(context));
-	}
-
-	private AuthnRequest createAuthnRequest(String issuer, String destination, String assertionConsumerServiceUrl,
-			String protocolBinding) {
-		AuthnRequest auth = this.authnRequestBuilder.buildObject();
-		auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
-		auth.setIssueInstant(new DateTime(this.clock.millis()));
-		auth.setForceAuthn(Boolean.FALSE);
-		auth.setIsPassive(Boolean.FALSE);
-		auth.setProtocolBinding(protocolBinding);
-		Issuer iss = this.issuerBuilder.buildObject();
-		iss.setValue(issuer);
-		auth.setIssuer(iss);
-		auth.setDestination(destination);
-		auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
-		return auth;
-	}
-
-	/**
-	 * Set the {@link AuthnRequest} post-processor resolver
-	 * @param authenticationRequestContextConverter
-	 * @since 5.4
-	 */
-	public void setAuthenticationRequestContextConverter(
-			Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter) {
-		Assert.notNull(authenticationRequestContextConverter, "authenticationRequestContextConverter cannot be null");
-		this.authenticationRequestContextConverter = authenticationRequestContextConverter;
-	}
-
-	/**
-	 * ' Use this {@link Clock} with {@link Instant#now()} for generating timestamps
-	 * @param clock
-	 */
-	public void setClock(Clock clock) {
-		Assert.notNull(clock, "clock cannot be null");
-		this.clock = clock;
-	}
-
-	/**
-	 * Sets the {@code protocolBinding} to use when generating authentication requests.
-	 * Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and
-	 * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} The IDP will be reading this value
-	 * in the {@code AuthNRequest} to determine how to send the Response/Assertion to the
-	 * ACS URL, assertion consumer service URL.
-	 * @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or
-	 * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
-	 * @throws IllegalArgumentException if the protocolBinding is not valid
-	 * @deprecated Use
-	 * {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder#assertionConsumerServiceBinding(Saml2MessageBinding)}
-	 * instead
-	 */
-	@Deprecated
-	public void setProtocolBinding(String protocolBinding) {
-		boolean isAllowedBinding = SAMLConstants.SAML2_POST_BINDING_URI.equals(protocolBinding)
-				|| SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(protocolBinding);
-		if (!isAllowedBinding) {
-			throw new IllegalArgumentException("Invalid protocol binding: " + protocolBinding);
-		}
-		this.protocolBindingResolver = (context) -> protocolBinding;
-	}
-
-	private AuthnRequest sign(AuthnRequest authnRequest, RelyingPartyRegistration relyingPartyRegistration) {
-		SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
-		return sign(authnRequest, parameters);
-	}
-
-	private AuthnRequest sign(AuthnRequest authnRequest, SignatureSigningParameters parameters) {
-		try {
-			SignatureSupport.signObject(authnRequest, parameters);
-			return authnRequest;
-		}
-		catch (Exception ex) {
-			throw new Saml2Exception(ex);
-		}
-	}
-
-	private void sign(Map<String, String> components, RelyingPartyRegistration relyingPartyRegistration) {
-		SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
-		sign(components, parameters);
-	}
-
-	private void sign(Map<String, String> components, SignatureSigningParameters parameters) {
-		Credential credential = parameters.getSigningCredential();
-		String algorithmUri = parameters.getSignatureAlgorithm();
-		components.put("SigAlg", algorithmUri);
-		UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
-		for (Map.Entry<String, String> component : components.entrySet()) {
-			builder.queryParam(component.getKey(), UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1));
-		}
-		String queryString = builder.build(true).toString().substring(1);
-		try {
-			byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri,
-					queryString.getBytes(StandardCharsets.UTF_8));
-			String b64Signature = Saml2Utils.samlEncode(rawSignature);
-			components.put("Signature", b64Signature);
-		}
-		catch (SecurityException ex) {
-			throw new Saml2Exception(ex);
-		}
-	}
-
-	private String serialize(AuthnRequest authnRequest) {
-		try {
-			Element element = this.marshaller.marshall(authnRequest);
-			return SerializeSupport.nodeToString(element);
-		}
-		catch (MarshallingException ex) {
-			throw new Saml2Exception(ex);
-		}
-	}
-
-	private SignatureSigningParameters resolveSigningParameters(RelyingPartyRegistration relyingPartyRegistration) {
-		List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
-		List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
-		List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
-		String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
-		SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
-		CriteriaSet criteria = new CriteriaSet();
-		BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration();
-		signingConfiguration.setSigningCredentials(credentials);
-		signingConfiguration.setSignatureAlgorithms(algorithms);
-		signingConfiguration.setSignatureReferenceDigestMethods(digests);
-		signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization);
-		criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
-		try {
-			SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
-			Assert.notNull(parameters, "Failed to resolve any signing credential");
-			return parameters;
-		}
-		catch (Exception ex) {
-			throw new Saml2Exception(ex);
-		}
-	}
-
-	private List<Credential> resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) {
-		List<Credential> credentials = new ArrayList<>();
-		for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) {
-			X509Certificate certificate = x509Credential.getCertificate();
-			PrivateKey privateKey = x509Credential.getPrivateKey();
-			BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey);
-			credential.setEntityId(relyingPartyRegistration.getEntityId());
-			credential.setUsageType(UsageType.SIGNING);
-			credentials.add(credential);
-		}
-		return credentials;
-	}
-
-}

+ 7 - 1
samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle

@@ -16,9 +16,15 @@
 
 apply plugin: 'io.spring.convention.spring-sample-boot'
 
+sourceCompatibility = '11'
+
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
 dependencies {
 	compile project(':spring-security-config')
-	compile project(':spring-security-saml2-service-provider')
+	compile project(':saml2-service-provider-opensaml4')
 	compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
 	compile 'org.springframework.boot:spring-boot-starter-web'
 	compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'

+ 5 - 1
samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2login.gradle

@@ -1,7 +1,11 @@
 apply plugin: 'io.spring.convention.spring-sample-war'
 
+repositories {
+	maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
+}
+
 dependencies {
-	compile project(':spring-security-saml2-service-provider')
+	compile project(':saml2-service-provider-opensaml3')
 	compile project(':spring-security-config')
 	compile slf4jDependencies