parent
43f2904059
commit
2276fcf34a
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import javax.xml.XMLConstants;
|
||||
|
||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.config.InitializationService;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.setParserPool;
|
||||
|
||||
/**
|
||||
* An initialization service for initializing OpenSAML. Each Spring Security OpenSAML-based component invokes
|
||||
* the {@link #initialize()} method at static initialization time.
|
||||
*
|
||||
* {@link #initialize()} is idempotent and may be safely called in custom classes that need OpenSAML to be
|
||||
* initialized in order to function correctly. It's recommended that you call this {@link #initialize()} method
|
||||
* when using Spring Security and OpenSAML instead of OpenSAML's {@link InitializationService#initialize()}.
|
||||
*
|
||||
* The primary purpose of {@link #initialize()} is to prepare OpenSAML's {@link XMLObjectProviderRegistry}
|
||||
* with some reasonable defaults. Any changes that Spring Security makes to the registry happen in this method.
|
||||
*
|
||||
* To override those defaults, call {@link #requireInitialize(Consumer)} and change the registry:
|
||||
*
|
||||
* <pre>
|
||||
* static {
|
||||
* OpenSamlInitializationService.requireInitialize(registry -> {
|
||||
* registry.setParserPool(...);
|
||||
* registry.getBuilderFactory().registerBuilder(...);
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* {@link #requireInitialize(Consumer)} may only be called once per application.
|
||||
*
|
||||
* If the application already initialized OpenSAML before {@link #requireInitialize(Consumer)} was called,
|
||||
* then the configuration changes will not be applied and an exception will be thrown. The reason for this is to
|
||||
* alert you to the fact that there are likely some initialization ordering problems in your application that
|
||||
* would otherwise lead to an unpredictable state.
|
||||
*
|
||||
* If you must change the registry's configuration in multiple places in your application, you are expected
|
||||
* to handle the initialization ordering issues yourself instead of trying to call {@link #requireInitialize(Consumer)}
|
||||
* multiple times.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.4
|
||||
*/
|
||||
public class OpenSamlInitializationService {
|
||||
private static final Log log = LogFactory.getLog(OpenSamlInitializationService.class);
|
||||
private static final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Ready OpenSAML for use and configure it with reasonable defaults.
|
||||
*
|
||||
* Initialization is guaranteed to happen only once per application. This method will passively return
|
||||
* {@code false} if initialization already took place earlier in the application.
|
||||
*
|
||||
* @return whether or not initialization was performed. The first thread to initialize OpenSAML will
|
||||
* return {@code true} while the rest will return {@code false}.
|
||||
* @throws Saml2Exception if OpenSAML failed to initialize
|
||||
*/
|
||||
public static boolean initialize() {
|
||||
return initialize(registry -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ready OpenSAML for use, configure it with reasonable defaults, and modify the {@link XMLObjectProviderRegistry}
|
||||
* using the provided {@link Consumer}.
|
||||
*
|
||||
* Initialization is guaranteed to happen only once per application. This method will throw an exception
|
||||
* if initialization already took place earlier in the application.
|
||||
*
|
||||
* @param registryConsumer the {@link Consumer} to further configure the {@link XMLObjectProviderRegistry}
|
||||
* @throws Saml2Exception if initialization already happened previously or if OpenSAML failed to initialize
|
||||
*/
|
||||
public static void requireInitialize(Consumer<XMLObjectProviderRegistry> registryConsumer) {
|
||||
if (!initialize(registryConsumer)) {
|
||||
throw new Saml2Exception("OpenSAML was already initialized previously");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean initialize(Consumer<XMLObjectProviderRegistry> registryConsumer) {
|
||||
if (initialized.compareAndSet(false, true)) {
|
||||
log.trace("Initializing OpenSAML");
|
||||
|
||||
try {
|
||||
InitializationService.initialize();
|
||||
} catch (Exception e) {
|
||||
throw new Saml2Exception(e);
|
||||
}
|
||||
|
||||
BasicParserPool parserPool = new BasicParserPool();
|
||||
parserPool.setMaxPoolSize(50);
|
||||
|
||||
Map<String, Boolean> parserBuilderFeatures = new HashMap<>();
|
||||
parserBuilderFeatures.put("http://apache.org/xml/features/disallow-doctype-decl", TRUE);
|
||||
parserBuilderFeatures.put(XMLConstants.FEATURE_SECURE_PROCESSING, TRUE);
|
||||
parserBuilderFeatures.put("http://xml.org/sax/features/external-general-entities", FALSE);
|
||||
parserBuilderFeatures.put("http://apache.org/xml/features/validation/schema/normalized-value", FALSE);
|
||||
parserBuilderFeatures.put("http://xml.org/sax/features/external-parameter-entities", FALSE);
|
||||
parserBuilderFeatures.put("http://apache.org/xml/features/dom/defer-node-expansion", FALSE);
|
||||
parserPool.setBuilderFeatures(parserBuilderFeatures);
|
||||
|
||||
try {
|
||||
parserPool.initialize();
|
||||
} catch (Exception e) {
|
||||
throw new Saml2Exception(e);
|
||||
}
|
||||
setParserPool(parserPool);
|
||||
|
||||
registryConsumer.accept(ConfigurationService.get(XMLObjectProviderRegistry.class));
|
||||
|
||||
log.debug("Initialized OpenSAML");
|
||||
return true;
|
||||
} else {
|
||||
log.debug("Refused to re-initialize OpenSAML");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -98,6 +98,7 @@ import org.springframework.security.core.GrantedAuthority;
|
|||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -160,6 +161,10 @@ import static org.springframework.util.Assert.notNull;
|
|||
*/
|
||||
public final class OpenSamlAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private static Log logger = LogFactory.getLog(OpenSamlAuthenticationProvider.class);
|
||||
|
||||
private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
|
||||
|
@ -270,7 +275,6 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
|
|||
} catch (Saml2Exception x) {
|
||||
throw authException(MALFORMED_RESPONSE_DATA, x.getMessage(), x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void process(Saml2AuthenticationToken token, Response response) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
@ -24,7 +25,6 @@ import java.util.Map;
|
|||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.opensaml.core.xml.io.MarshallingException;
|
||||
|
@ -43,6 +43,7 @@ import org.opensaml.xmlsec.signature.support.SignatureSupport;
|
|||
|
||||
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;
|
||||
|
@ -56,6 +57,10 @@ import static org.springframework.security.saml2.provider.service.authentication
|
|||
* @since 5.2
|
||||
*/
|
||||
public class OpenSamlAuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
|
||||
|
||||
|
|
|
@ -20,22 +20,14 @@ import java.io.ByteArrayInputStream;
|
|||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
|
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
|
||||
import net.shibboleth.utilities.java.support.xml.XMLParserException;
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.config.InitializationException;
|
||||
import org.opensaml.core.config.InitializationService;
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.core.xml.XMLObjectBuilderFactory;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.core.xml.io.MarshallerFactory;
|
||||
import org.opensaml.core.xml.io.MarshallingException;
|
||||
|
@ -62,24 +54,27 @@ import org.w3c.dom.Document;
|
|||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getParserPool;
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
final class OpenSamlImplementation {
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private static OpenSamlImplementation instance = new OpenSamlImplementation();
|
||||
private static XMLObjectBuilderFactory xmlObjectBuilderFactory =
|
||||
XMLObjectProviderRegistrySupport.getBuilderFactory();
|
||||
|
||||
private final BasicParserPool parserPool = new BasicParserPool();
|
||||
private final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
|
||||
asList(
|
||||
new InlineEncryptedKeyResolver(),
|
||||
|
@ -88,74 +83,6 @@ final class OpenSamlImplementation {
|
|||
)
|
||||
);
|
||||
|
||||
private OpenSamlImplementation() {
|
||||
bootstrap();
|
||||
}
|
||||
|
||||
/*
|
||||
* ==============================================================
|
||||
* PRIVATE METHODS
|
||||
* ==============================================================
|
||||
*/
|
||||
private void bootstrap() {
|
||||
// configure default values
|
||||
// maxPoolSize = 5;
|
||||
this.parserPool.setMaxPoolSize(50);
|
||||
// coalescing = true;
|
||||
this.parserPool.setCoalescing(true);
|
||||
// expandEntityReferences = false;
|
||||
this.parserPool.setExpandEntityReferences(false);
|
||||
// ignoreComments = true;
|
||||
this.parserPool.setIgnoreComments(true);
|
||||
// ignoreElementContentWhitespace = true;
|
||||
this.parserPool.setIgnoreElementContentWhitespace(true);
|
||||
// namespaceAware = true;
|
||||
this.parserPool.setNamespaceAware(true);
|
||||
// schema = null;
|
||||
this.parserPool.setSchema(null);
|
||||
// dtdValidating = false;
|
||||
this.parserPool.setDTDValidating(false);
|
||||
// xincludeAware = false;
|
||||
this.parserPool.setXincludeAware(false);
|
||||
|
||||
Map<String, Object> builderAttributes = new HashMap<>();
|
||||
this.parserPool.setBuilderAttributes(builderAttributes);
|
||||
|
||||
Map<String, Boolean> parserBuilderFeatures = new HashMap<>();
|
||||
parserBuilderFeatures.put("http://apache.org/xml/features/disallow-doctype-decl", TRUE);
|
||||
parserBuilderFeatures.put(XMLConstants.FEATURE_SECURE_PROCESSING, TRUE);
|
||||
parserBuilderFeatures.put("http://xml.org/sax/features/external-general-entities", FALSE);
|
||||
parserBuilderFeatures.put("http://apache.org/xml/features/validation/schema/normalized-value", FALSE);
|
||||
parserBuilderFeatures.put("http://xml.org/sax/features/external-parameter-entities", FALSE);
|
||||
parserBuilderFeatures.put("http://apache.org/xml/features/dom/defer-node-expansion", FALSE);
|
||||
this.parserPool.setBuilderFeatures(parserBuilderFeatures);
|
||||
|
||||
try {
|
||||
this.parserPool.initialize();
|
||||
}
|
||||
catch (ComponentInitializationException x) {
|
||||
throw new Saml2Exception("Unable to initialize OpenSaml v3 ParserPool", x);
|
||||
}
|
||||
|
||||
try {
|
||||
InitializationService.initialize();
|
||||
}
|
||||
catch (InitializationException e) {
|
||||
throw new Saml2Exception("Unable to initialize OpenSaml v3", e);
|
||||
}
|
||||
|
||||
XMLObjectProviderRegistry registry;
|
||||
synchronized (ConfigurationService.class) {
|
||||
registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||
if (registry == null) {
|
||||
registry = new XMLObjectProviderRegistry();
|
||||
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
|
||||
}
|
||||
}
|
||||
|
||||
registry.setParserPool(this.parserPool);
|
||||
}
|
||||
|
||||
/*
|
||||
* ==============================================================
|
||||
* PUBLIC METHODS
|
||||
|
@ -259,7 +186,7 @@ final class OpenSamlImplementation {
|
|||
|
||||
private XMLObject parse(byte[] xml) {
|
||||
try {
|
||||
Document document = this.parserPool.parse(new ByteArrayInputStream(xml));
|
||||
Document document = getParserPool().parse(new ByteArrayInputStream(xml));
|
||||
Element element = document.getDocumentElement();
|
||||
return getUnmarshallerFactory().getUnmarshaller(element).unmarshall(element);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 org.junit.Test;
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlInitializationService}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
public class OpenSamlInitializationServiceTests {
|
||||
|
||||
@Test
|
||||
public void initializeWhenInvokedMultipleTimesThenInitializesOnce() {
|
||||
OpenSamlInitializationService.initialize();
|
||||
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||
assertThat(registry.getParserPool()).isNotNull();
|
||||
registry.setParserPool(null);
|
||||
OpenSamlInitializationService.initialize();
|
||||
assertThat(registry.getParserPool()).isNull();
|
||||
assertThatCode(() -> OpenSamlInitializationService.requireInitialize(r -> {}))
|
||||
.isInstanceOf(Saml2Exception.class)
|
||||
.hasMessageContaining("OpenSAML was already initialized previously");
|
||||
}
|
||||
}
|
|
@ -74,9 +74,14 @@ import org.opensaml.xmlsec.signature.support.SignatureException;
|
|||
import org.opensaml.xmlsec.signature.support.SignatureSupport;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
|
||||
final class TestOpenSamlObjects {
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private static OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
|
||||
|
||||
private static String USERNAME = "test@saml.user";
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
|
||||
import org.apache.xml.security.algorithms.JCEMapper;
|
||||
import org.apache.xml.security.encryption.XMLCipherParameters;
|
||||
|
@ -54,14 +61,9 @@ import org.opensaml.security.credential.CredentialSupport;
|
|||
import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
|
||||
import org.opensaml.xmlsec.encryption.support.EncryptionException;
|
||||
import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.opensaml.security.crypto.KeySupport.generateKey;
|
||||
|
@ -73,6 +75,10 @@ import static org.opensaml.security.crypto.KeySupport.generateKey;
|
|||
*/
|
||||
public class OpenSamlActionTestingSupport {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
/** ID used for all generated {@link Response} objects. */
|
||||
final static String REQUEST_ID = "request";
|
||||
|
||||
|
|
Loading…
Reference in New Issue