From 9329d662f8bf300087c69225756c9e0c15c4eae9 Mon Sep 17 00:00:00 2001
From: Josh Cummings
Date: Tue, 23 Mar 2021 16:53:37 -0600
Subject: [PATCH 1/2] Add Saml2MessageBinding#from
Closes gh-9515
---
.../service/registration/Saml2MessageBinding.java | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
index 958f608e7e2..50309e504da 100644
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
+++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
@@ -43,4 +43,18 @@ public String getUrn() {
return this.urn;
}
+ /**
+ * Attempt to resolve the provided algorithm name to a {@code Saml2MessageBinding}.
+ * @param name the algorithm name
+ * @return the resolved {@code Saml2MessageBinding}, or {@code null} if not found
+ * @since 5.5
+ */
+ public static Saml2MessageBinding from(String name) {
+ for (Saml2MessageBinding value : values()) {
+ if (value.getUrn().equals(name)) {
+ return value;
+ }
+ }
+ return null;
+ }
}
From 9a1d4fd46e0605d904ecc3936aefc3523dc22b7c Mon Sep 17 00:00:00 2001
From: Josh Cummings
Date: Mon, 7 Dec 2020 12:28:19 -0700
Subject: [PATCH 2/2] Add OpenSAML 4 Support
Closes gh-9095
---
config/spring-security-config.gradle | 10 +-
.../saml2/Saml2LoginConfigurer.java | 20 +-
.../saml2/Saml2LoginConfigurerTests.java | 14 +-
.../core/saml2-service-provider-core.gradle | 53 ++
.../security/saml2/Saml2Exception.java | 0
.../core/OpenSamlInitializationService.java | 0
.../security/saml2/core/Saml2Error.java | 0
.../security/saml2/core/Saml2ErrorCodes.java | 0
.../core/Saml2ResponseValidatorResult.java | 0
.../saml2/core/Saml2X509Credential.java | 0
.../SpringSecurityAuthnRequestBuilder.java | 80 ++
.../credentials/Saml2X509Credential.java | 0
.../AbstractSaml2AuthenticationRequest.java | 0
.../DefaultSaml2AuthenticatedPrincipal.java | 0
.../OpenSamlDecryptionUtils.java | 113 +++
.../authentication/OpenSamlSigningUtils.java | 173 ++++
.../OpenSamlVerificationUtils.java | 217 +++++
.../Saml2AuthenticatedPrincipal.java | 0
.../authentication/Saml2Authentication.java | 0
.../Saml2AuthenticationException.java | 0
.../Saml2AuthenticationRequest.java | 0
.../Saml2AuthenticationRequestContext.java | 0
.../Saml2AuthenticationRequestFactory.java | 0
.../Saml2AuthenticationToken.java | 0
.../service/authentication/Saml2Error.java | 0
.../authentication/Saml2ErrorCodes.java | 0
.../Saml2PostAuthenticationRequest.java | 0
.../Saml2RedirectAuthenticationRequest.java | 0
.../service/authentication/Saml2Utils.java | 0
.../metadata/OpenSamlMetadataResolver.java | 0
.../metadata/Saml2MetadataResolver.java | 0
...oryRelyingPartyRegistrationRepository.java | 0
...enSamlAssertingPartyMetadataConverter.java | 0
...gistrationBuilderHttpMessageConverter.java | 0
.../RelyingPartyRegistration.java | 0
.../RelyingPartyRegistrationRepository.java | 0
.../RelyingPartyRegistrations.java | 0
.../registration/Saml2MessageBinding.java | 1 +
.../Saml2WebSsoAuthenticationFilter.java | 0
...aml2WebSsoAuthenticationRequestFilter.java | 20 +-
...faultRelyingPartyRegistrationResolver.java | 0
...2AuthenticationRequestContextResolver.java | 0
...2AuthenticationRequestContextResolver.java | 0
.../Saml2AuthenticationTokenConverter.java | 0
.../service/web/Saml2MetadataFilter.java | 0
.../OpenSamlInitializationServiceTests.java | 0
.../Saml2ResponseValidatorResultTests.java | 0
.../security/saml2/core/Saml2Utils.java | 0
.../saml2/core/Saml2X509CredentialTests.java | 0
.../saml2/core/TestSaml2X509Credentials.java | 0
.../credentials/Saml2X509CredentialTests.java | 0
.../credentials/TestSaml2X509Credentials.java | 0
...faultSaml2AuthenticatedPrincipalTests.java | 0
...aml2AuthenticationRequestFactoryTests.java | 0
.../authentication/TestOpenSamlObjects.java | 21 +-
...estSaml2AuthenticationRequestContexts.java | 0
.../OpenSamlMetadataResolverTests.java | 0
...lAssertingPartyMetadataConverterTests.java | 0
...ationBuilderHttpMessageConverterTests.java | 0
.../RelyingPartyRegistrationTests.java | 0
.../RelyingPartyRegistrationsTests.java | 0
.../TestRelyingPartyRegistrations.java | 0
.../Saml2WebSsoAuthenticationFilterTests.java | 0
...ebSsoAuthenticationRequestFilterTests.java | 139 ++--
...RelyingPartyRegistrationResolverTests.java | 0
...enticationRequestContextResolverTests.java | 0
...aml2AuthenticationTokenConverterTests.java | 0
.../service/web/Saml2MetadataFilterTests.java | 0
.../src/test/resources/logback-test.xml | 0
.../saml2-response-sso-circle.encoded | 0
.../src/test/resources/test-metadata.xml | 0
.../saml2-service-provider-opensaml3.gradle | 53 ++
.../OpenSamlAuthenticationProvider.java | 162 +---
.../OpenSamlAuthenticationRequestFactory.java | 203 +++++
.../OpenSamlAuthenticationProviderTests.java | 141 ++--
...SamlAuthenticationRequestFactoryTests.java | 13 +-
.../saml2-service-provider-opensaml4.gradle | 58 ++
.../OpenSaml4AuthenticationProvider.java | 770 ++++++++++++++++++
...OpenSaml4AuthenticationRequestFactory.java | 180 ++++
.../OpenSaml4AuthenticationProviderTests.java | 661 +++++++++++++++
...aml4AuthenticationRequestFactoryTests.java | 274 +++++++
...ing-security-saml2-service-provider.gradle | 50 +-
.../OpenSamlAuthenticationRequestFactory.java | 311 -------
...ng-security-samples-boot-saml2login.gradle | 8 +-
...urity-samples-javaconfig-saml2login.gradle | 6 +-
85 files changed, 3137 insertions(+), 614 deletions(-)
create mode 100644 saml2/saml2-service-provider/core/saml2-service-provider-core.gradle
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/Saml2Exception.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/core/Saml2Error.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java (100%)
create mode 100644 saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/SpringSecurityAuthnRequestBuilder.java
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java (100%)
create mode 100644 saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlDecryptionUtils.java
create mode 100644 saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlSigningUtils.java
create mode 100644 saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlVerificationUtils.java
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java (99%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java (93%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java (92%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java (58%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolverTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/resources/logback-test.xml (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/resources/saml2-response-sso-circle.encoded (100%)
rename saml2/saml2-service-provider/{ => core}/src/test/resources/test-metadata.xml (100%)
create mode 100644 saml2/saml2-service-provider/opensaml3/saml2-service-provider-opensaml3.gradle
rename saml2/saml2-service-provider/{ => opensaml3}/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java (82%)
create mode 100644 saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
rename saml2/saml2-service-provider/{ => opensaml3}/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java (88%)
rename saml2/saml2-service-provider/{ => opensaml3}/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java (98%)
create mode 100644 saml2/saml2-service-provider/opensaml4/saml2-service-provider-opensaml4.gradle
create mode 100644 saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java
create mode 100644 saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java
create mode 100644 saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java
create mode 100644 saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java
delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle
index 67024437f62..03cfeddff83 100644
--- a/config/spring-security-config.gradle
+++ b/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
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java
index 4adad193d64..196878c5787 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java
@@ -21,15 +21,20 @@
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 @@ protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingU
* The {@code loginProcessingUrl} is set
* A custom login page is configured, or
* A default login page with all SAML 2.0 Identity Providers is configured
- * An {@link OpenSamlAuthenticationProvider} is configured
+ * An {@link AuthenticationProvider} is configured
*
*/
@Override
@@ -256,8 +261,12 @@ private AuthenticationConverter getAuthenticationConverter(B http) {
}
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 @@ private Filter build(B http) {
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;
}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
index 4d79181b3fa..a04c2bbb22d 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
@@ -19,6 +19,7 @@
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.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 @@ private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
"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 @@ protected void configure(HttpSecurity http) throws Exception {
@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;
});
diff --git a/saml2/saml2-service-provider/core/saml2-service-provider-core.gradle b/saml2/saml2-service-provider/core/saml2-service-provider-core.gradle
new file mode 100644
index 00000000000..6a64caa2fc7
--- /dev/null
+++ b/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'
+}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/Saml2Exception.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/Saml2Exception.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/Saml2Exception.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/OpenSamlInitializationService.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2Error.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2Error.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2Error.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResult.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2X509Credential.java
diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/SpringSecurityAuthnRequestBuilder.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/SpringSecurityAuthnRequestBuilder.java
new file mode 100644
index 00000000000..c8cd19399f6
--- /dev/null
+++ b/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 builder;
+
+ private Clock clock = Clock.systemUTC();
+
+ SpringSecurityAuthnRequestBuilder(XMLObjectBuilder 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);
+ }
+ }
+
+}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/credentials/Saml2X509Credential.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipal.java
diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlDecryptionUtils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlDecryptionUtils.java
new file mode 100644
index 00000000000..e80bd0619b6
--- /dev/null
+++ b/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 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() {
+ }
+
+}
diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlSigningUtils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlSigningUtils.java
new file mode 100644
index 00000000000..f177a866b44
--- /dev/null
+++ b/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 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 credentials = resolveSigningCredentials(relyingPartyRegistration);
+ List algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
+ List 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 resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) {
+ List 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 components = new LinkedHashMap<>();
+
+ QueryParametersPartial(RelyingPartyRegistration registration) {
+ this.registration = registration;
+ }
+
+ QueryParametersPartial param(String key, String value) {
+ this.components.put(key, value);
+ return this;
+ }
+
+ Map 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 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() {
+
+ }
+
+}
diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlVerificationUtils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlVerificationUtils.java
new file mode 100644
index 00000000000..00dbfd7af43
--- /dev/null
+++ b/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 credentials = new HashSet<>();
+ Collection 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 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 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() {
+
+ }
+
+}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationException.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationToken.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Error.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2ErrorCodes.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/Saml2MetadataResolver.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
similarity index 99%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
index 50309e504da..015aa81d96b 100644
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
+++ b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java
@@ -57,4 +57,5 @@ public static Saml2MessageBinding from(String name) {
}
return null;
}
+
}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java
similarity index 93%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java
index 731c6a3c669..026296e9e8e 100644
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java
+++ b/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.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.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);
+ }
}
/**
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationRequestContextResolver.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java
similarity index 100%
rename from saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java
rename to saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/OpenSamlInitializationServiceTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2ResponseValidatorResultTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2Utils.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/Saml2X509CredentialTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/core/TestSaml2X509Credentials.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/Saml2X509CredentialTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/credentials/TestSaml2X509Credentials.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/DefaultSaml2AuthenticatedPrincipalTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java
similarity index 92%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java
index 40e1d8bfe14..d7a1fcdc34e 100644
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java
+++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java
@@ -28,21 +28,17 @@
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 @@ static Response response() {
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 @@ static Assertion assertion() {
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 @@ static SubjectConfirmation subjectConfirmation() {
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 @@ static List attributeStatements() {
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;
}
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestSaml2AuthenticationRequestContexts.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java
similarity index 100%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java
similarity index 58%
rename from saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java
rename to saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java
index 6079de5bcb9..3f0bf6cf843 100644
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java
+++ b/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.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 void setup() {
@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 void doFilterWhenRelayStateThatRequiresEncodingThenRedirectDoesContainsEn
@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 void doFilterWhenPostFormDataIsPresent() throws Exception {
@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("
+ *
+ * This provider does not perform an X509 certificate validation on the configured
+ * asserting party, IDP, verification certificates.
+ *
+ *
+ * @author Josh Cummings
+ * @since 5.5
+ * @see SAML 2
+ * StatusResponse
+ * @see OpenSAML 3
+ */
+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 responseSignatureValidator = createDefaultResponseSignatureValidator();
+
+ private Consumer responseElementsDecrypter = createDefaultResponseElementsDecrypter();
+
+ private final Converter responseValidator = createDefaultResponseValidator();
+
+ private final Converter assertionSignatureValidator = createDefaultAssertionSignatureValidator();
+
+ private Consumer assertionElementsDecrypter = createDefaultAssertionElementsDecrypter();
+
+ private Converter assertionValidator = createDefaultAssertionValidator();
+
+ private Converter 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:
+ *
+ *
+ * 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(...);
+ * }
+ * });
+ *
+ *
+ * Or, in the event that you have your own custom decryption interface, the same
+ * pattern applies:
+ *
+ *
+ * OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+ * Converter<EncryptedAssertion, Assertion> myService = ...
+ * provider.setResponseDecrypter((responseToken) -> {
+ * Response response = responseToken.getResponse();
+ * response.getEncryptedAssertions().stream()
+ * .map(service::decrypt).forEach(response.getAssertions()::add);
+ * });
+ *
+ *
+ * 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 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:
+ *
+ *
+ * OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+ * provider.setAssertionValidator(assertionToken -> {
+ * Saml2ResponseValidatorResult result = createDefaultAssertionValidator()
+ * .convert(assertionToken)
+ * return result.concat(myCustomValidator.convert(assertionToken));
+ * });
+ *
+ *
+ * You can also use this method to configure the provider to use a different
+ * {@link ValidationContext} from the default, like so:
+ *
+ *
+ * OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+ * provider.setAssertionValidator(
+ * createDefaultAssertionValidator(assertionToken -> {
+ * Map<String, Object> params = new HashMap<>();
+ * params.put(CLOCK_SKEW, 2 * 60 * 1000);
+ * // other parameters
+ * return new ValidationContext(params);
+ * }));
+ *
+ *
+ * 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 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:
+ *
+ *
+ * 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(...);
+ * }
+ * });
+ *
+ *
+ * Or, in the event that you have your own custom interface, the same pattern applies:
+ *
+ *
+ * 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);
+ * });
+ *
+ * @param assertionDecrypter the {@link Consumer} for decrypting assertion elements
+ * @since 5.5
+ */
+ public void setAssertionElementsDecrypter(Consumer 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:
+ *
+ *
+ * OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+ * Converter<ResponseToken, Saml2Authentication> authenticationConverter =
+ * createDefaultResponseAuthenticationConverter();
+ * provider.setResponseAuthenticationConverter(responseToken -> {
+ * Saml2Authentication authentication = authenticationConverter.convert(responseToken);
+ * User user = myUserRepository.findByUsername(authentication.getName());
+ * return new MyAuthentication(authentication, user);
+ * });
+ *
+ * @param responseAuthenticationConverter the {@link Converter} to use
+ * @since 5.4
+ */
+ public void setResponseAuthenticationConverter(
+ Converter 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 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 createDefaultAssertionValidator(
+ Converter 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 createDefaultResponseAuthenticationConverter() {
+ return (responseToken) -> {
+ Response response = responseToken.response;
+ Saml2AuthenticationToken token = responseToken.token;
+ Assertion assertion = CollectionUtils.firstElement(response.getAssertions());
+ String username = assertion.getSubject().getNameID().getValue();
+ Map> 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 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 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 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 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 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 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> getAssertionAttributes(Assertion assertion) {
+ Map> attributeMap = new LinkedHashMap<>();
+ for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
+ for (Attribute attribute : attributeStatement.getAttributes()) {
+ List 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 createAssertionValidator(String errorCode,
+ Converter validatorConverter,
+ Converter 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> paramsConsumer) {
+ String audience = assertionToken.token.getRelyingPartyRegistration().getEntityId();
+ String recipient = assertionToken.token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation();
+ Map 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 conditions = new ArrayList<>();
+
+ private static final Collection subjects = new ArrayList<>();
+
+ private static final Collection 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;
+ }
+
+ }
+
+}
diff --git a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java
new file mode 100644
index 00000000000..30a3af18351
--- /dev/null
+++ b/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 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 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 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;
+ }
+
+}
diff --git a/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java b/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java
new file mode 100644
index 00000000000..bc94bc3b2b8
--- /dev/null
+++ b/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 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 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 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 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 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 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 errorOf(String errorCode) {
+ return errorOf(errorCode, null);
+ }
+
+ private Consumer 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 attributeStatements() {
+ List 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()));
+ }
+
+}
diff --git a/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/opensaml4/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java
new file mode 100644
index 00000000000..0297d10f7fe
--- /dev/null
+++ b/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(" 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 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 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);
+ }
+ }
+
+}
diff --git a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle
index c10aadf7082..ee978368fa9 100644
--- a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle
+++ b/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
}
diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
deleted file mode 100644
index 26bfd1a8489..00000000000
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java
+++ /dev/null
@@ -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 protocolBindingResolver = (context) -> {
- if (context == null) {
- return SAMLConstants.SAML2_POST_BINDING_URI;
- }
- return context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
- };
-
- private Converter 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 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 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 components, RelyingPartyRegistration relyingPartyRegistration) {
- SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
- sign(components, parameters);
- }
-
- private void sign(Map components, SignatureSigningParameters parameters) {
- Credential credential = parameters.getSigningCredential();
- String algorithmUri = parameters.getSignatureAlgorithm();
- components.put("SigAlg", algorithmUri);
- UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
- for (Map.Entry 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 credentials = resolveSigningCredentials(relyingPartyRegistration);
- List algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
- List 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 resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) {
- List 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;
- }
-
-}
diff --git a/samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle b/samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle
index f46ce4a8d98..ee78e3f1999 100644
--- a/samples/boot/saml2login/spring-security-samples-boot-saml2login.gradle
+++ b/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'
diff --git a/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2login.gradle b/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2login.gradle
index e1d82499dc9..beccb62a102 100644
--- a/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2login.gradle
+++ b/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