Create spring-boot-security-saml2 module

This commit is contained in:
Andy Wilkinson
2025-03-31 15:07:32 +01:00
committed by Phillip Webb
parent 4d8b558b9b
commit 4ce8b591e0
27 changed files with 84 additions and 31 deletions

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Registration;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Condition that matches if any {@code spring.security.saml2.relyingparty.registration}
* properties are defined.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class RegistrationConfiguredCondition extends SpringBootCondition {
private static final String PROPERTY = "spring.security.saml2.relyingparty.registration";
private static final Bindable<Map<String, Registration>> STRING_REGISTRATION_MAP = Bindable.mapOf(String.class,
Registration.class);
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Relying Party Registration Condition");
Map<String, Registration> registrations = getRegistrations(context.getEnvironment());
if (registrations.isEmpty()) {
return ConditionOutcome.noMatch(message.didNotFind("any registrations").atAll());
}
return ConditionOutcome.match(message.found("registration", "registrations").items(registrations.keySet()));
}
private Map<String, Registration> getRegistrations(Environment environment) {
return Binder.get(environment).bind(PROPERTY, STRING_REGISTRATION_MAP).orElse(Collections.emptyMap());
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.security.autoconfigure.ConditionalOnDefaultWebSecurity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link SecurityFilterChain} configuration for Spring Security's relying party SAML
* support.
*
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnBean(RelyingPartyRegistrationRepository.class)
class Saml2LoginConfiguration {
@Bean
SecurityFilterChain samlSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.saml2Login(withDefaults());
http.saml2Logout(withDefaults());
return http.build();
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security's SAML 2.0
* authentication support.
*
* @author Madhura Bhave
* @since 4.0.0
*/
@AutoConfiguration(before = SecurityAutoConfiguration.class)
@ConditionalOnClass(RelyingPartyRegistrationRepository.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ Saml2RelyingPartyRegistrationConfiguration.class, Saml2LoginConfiguration.class })
@EnableConfigurationProperties(Saml2RelyingPartyProperties.class)
public class Saml2RelyingPartyAutoConfiguration {
}

View File

@@ -1,430 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
/**
* SAML2 relying party properties.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Moritz Halbritter
* @author Lasse Wulff
* @since 4.0.0
*/
@ConfigurationProperties("spring.security.saml2.relyingparty")
public class Saml2RelyingPartyProperties {
/**
* SAML2 relying party registrations.
*/
private final Map<String, Registration> registration = new LinkedHashMap<>();
public Map<String, Registration> getRegistration() {
return this.registration;
}
/**
* Represents a SAML Relying Party.
*/
public static class Registration {
/**
* Relying party's entity ID. The value may contain a number of placeholders. They
* are "baseUrl", "registrationId", "baseScheme", "baseHost", and "basePort".
*/
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
/**
* Assertion Consumer Service.
*/
private final Acs acs = new Acs();
private final Signing signing = new Signing();
private final Decryption decryption = new Decryption();
private final Singlelogout singlelogout = new Singlelogout();
/**
* Remote SAML Identity Provider.
*/
private final AssertingParty assertingparty = new AssertingParty();
/**
* Name ID format for a relying party registration.
*/
private String nameIdFormat;
public String getEntityId() {
return this.entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public Acs getAcs() {
return this.acs;
}
public Signing getSigning() {
return this.signing;
}
public Decryption getDecryption() {
return this.decryption;
}
public Singlelogout getSinglelogout() {
return this.singlelogout;
}
public AssertingParty getAssertingparty() {
return this.assertingparty;
}
public String getNameIdFormat() {
return this.nameIdFormat;
}
public void setNameIdFormat(String nameIdFormat) {
this.nameIdFormat = nameIdFormat;
}
public static class Acs {
/**
* Assertion Consumer Service location template. Can generate its location
* based on possible variables of "baseUrl", "registrationId", "baseScheme",
* "baseHost", and "basePort".
*/
private String location = "{baseUrl}/login/saml2/sso/{registrationId}";
/**
* Assertion Consumer Service binding.
*/
private Saml2MessageBinding binding = Saml2MessageBinding.POST;
public String getLocation() {
return this.location;
}
public void setLocation(String location) {
this.location = location;
}
public Saml2MessageBinding getBinding() {
return this.binding;
}
public void setBinding(Saml2MessageBinding binding) {
this.binding = binding;
}
}
public static class Signing {
/**
* Credentials used for signing the SAML authentication request.
*/
private List<Credential> credentials = new ArrayList<>();
public List<Credential> getCredentials() {
return this.credentials;
}
public void setCredentials(List<Credential> credentials) {
this.credentials = credentials;
}
public static class Credential {
/**
* Private key used for signing.
*/
private Resource privateKeyLocation;
/**
* Relying Party X509Certificate shared with the identity provider.
*/
private Resource certificateLocation;
public Resource getPrivateKeyLocation() {
return this.privateKeyLocation;
}
public void setPrivateKeyLocation(Resource privateKey) {
this.privateKeyLocation = privateKey;
}
public Resource getCertificateLocation() {
return this.certificateLocation;
}
public void setCertificateLocation(Resource certificate) {
this.certificateLocation = certificate;
}
}
}
}
public static class Decryption {
/**
* Credentials used for decrypting the SAML authentication request.
*/
private List<Credential> credentials = new ArrayList<>();
public List<Credential> getCredentials() {
return this.credentials;
}
public void setCredentials(List<Credential> credentials) {
this.credentials = credentials;
}
public static class Credential {
/**
* Private key used for decrypting.
*/
private Resource privateKeyLocation;
/**
* Relying Party X509Certificate shared with the identity provider.
*/
private Resource certificateLocation;
public Resource getPrivateKeyLocation() {
return this.privateKeyLocation;
}
public void setPrivateKeyLocation(Resource privateKey) {
this.privateKeyLocation = privateKey;
}
public Resource getCertificateLocation() {
return this.certificateLocation;
}
public void setCertificateLocation(Resource certificate) {
this.certificateLocation = certificate;
}
}
}
/**
* Represents a remote Identity Provider.
*/
public static class AssertingParty {
/**
* Unique identifier for the identity provider.
*/
private String entityId;
/**
* URI to the metadata endpoint for discovery-based configuration.
*/
private String metadataUri;
private final Singlesignon singlesignon = new Singlesignon();
private final Verification verification = new Verification();
private final Singlelogout singlelogout = new Singlelogout();
public String getEntityId() {
return this.entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public String getMetadataUri() {
return this.metadataUri;
}
public void setMetadataUri(String metadataUri) {
this.metadataUri = metadataUri;
}
public Singlesignon getSinglesignon() {
return this.singlesignon;
}
public Verification getVerification() {
return this.verification;
}
public Singlelogout getSinglelogout() {
return this.singlelogout;
}
/**
* Single sign on details for an Identity Provider.
*/
public static class Singlesignon {
/**
* Remote endpoint to send authentication requests to.
*/
private String url;
/**
* Whether to redirect or post authentication requests.
*/
private Saml2MessageBinding binding;
/**
* Whether to sign authentication requests.
*/
private Boolean signRequest;
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public Saml2MessageBinding getBinding() {
return this.binding;
}
public void setBinding(Saml2MessageBinding binding) {
this.binding = binding;
}
public boolean isSignRequest() {
return this.signRequest;
}
public Boolean getSignRequest() {
return this.signRequest;
}
public void setSignRequest(Boolean signRequest) {
this.signRequest = signRequest;
}
}
/**
* Verification details for an Identity Provider.
*/
public static class Verification {
/**
* Credentials used for verification of incoming SAML messages.
*/
private List<Credential> credentials = new ArrayList<>();
public List<Credential> getCredentials() {
return this.credentials;
}
public void setCredentials(List<Credential> credentials) {
this.credentials = credentials;
}
public static class Credential {
/**
* Locations of the X.509 certificate used for verification of incoming
* SAML messages.
*/
private Resource certificate;
public Resource getCertificateLocation() {
return this.certificate;
}
public void setCertificateLocation(Resource certificate) {
this.certificate = certificate;
}
}
}
}
/**
* Single logout details.
*/
public static class Singlelogout {
/**
* Location where SAML2 LogoutRequest gets sent to.
*/
private String url;
/**
* Location where SAML2 LogoutResponse gets sent to.
*/
private String responseUrl;
/**
* Whether to redirect or post logout requests.
*/
private Saml2MessageBinding binding;
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getResponseUrl() {
return this.responseUrl;
}
public void setResponseUrl(String responseUrl) {
this.responseUrl = responseUrl;
}
public Saml2MessageBinding getBinding() {
return this.binding;
}
public void setBinding(Saml2MessageBinding binding) {
this.binding = binding;
}
}
}

View File

@@ -1,200 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.AssertingParty;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.AssertingParty.Verification;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Decryption;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Registration;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Registration.Signing;
import org.springframework.boot.ssl.pem.PemContent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link Configuration @Configuration} used to map {@link Saml2RelyingPartyProperties} to
* relying party registrations in a {@link RelyingPartyRegistrationRepository}.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Moritz Halbritter
* @author Lasse Lindqvist
* @author Lasse Wulff
* @author Scott Frederick
*/
@Configuration(proxyBeanMethods = false)
@Conditional(RegistrationConfiguredCondition.class)
@ConditionalOnMissingBean(RelyingPartyRegistrationRepository.class)
class Saml2RelyingPartyRegistrationConfiguration {
@Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(Saml2RelyingPartyProperties properties) {
List<RelyingPartyRegistration> registrations = properties.getRegistration()
.entrySet()
.stream()
.map(this::asRegistration)
.toList();
return new InMemoryRelyingPartyRegistrationRepository(registrations);
}
private RelyingPartyRegistration asRegistration(Map.Entry<String, Registration> entry) {
return asRegistration(entry.getKey(), entry.getValue());
}
private RelyingPartyRegistration asRegistration(String id, Registration properties) {
boolean usingMetadata = StringUtils.hasText(properties.getAssertingparty().getMetadataUri());
Builder builder = (!usingMetadata) ? RelyingPartyRegistration.withRegistrationId(id)
: createBuilderUsingMetadata(properties.getAssertingparty()).registrationId(id);
builder.assertionConsumerServiceLocation(properties.getAcs().getLocation());
builder.assertionConsumerServiceBinding(properties.getAcs().getBinding());
builder.assertingPartyMetadata(mapAssertingParty(properties.getAssertingparty()));
builder.signingX509Credentials((credentials) -> properties.getSigning()
.getCredentials()
.stream()
.map(this::asSigningCredential)
.forEach(credentials::add));
builder.decryptionX509Credentials((credentials) -> properties.getDecryption()
.getCredentials()
.stream()
.map(this::asDecryptionCredential)
.forEach(credentials::add));
builder.assertingPartyMetadata(
(details) -> details.verificationX509Credentials((credentials) -> properties.getAssertingparty()
.getVerification()
.getCredentials()
.stream()
.map(this::asVerificationCredential)
.forEach(credentials::add)));
builder.singleLogoutServiceLocation(properties.getSinglelogout().getUrl());
builder.singleLogoutServiceResponseLocation(properties.getSinglelogout().getResponseUrl());
builder.singleLogoutServiceBinding(properties.getSinglelogout().getBinding());
builder.entityId(properties.getEntityId());
builder.nameIdFormat(properties.getNameIdFormat());
RelyingPartyRegistration registration = builder.build();
boolean signRequest = registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned();
validateSigningCredentials(properties, signRequest);
return registration;
}
private RelyingPartyRegistration.Builder createBuilderUsingMetadata(AssertingParty properties) {
String requiredEntityId = properties.getEntityId();
Collection<Builder> candidates = RelyingPartyRegistrations
.collectionFromMetadataLocation(properties.getMetadataUri());
for (RelyingPartyRegistration.Builder candidate : candidates) {
if (requiredEntityId == null || requiredEntityId.equals(getEntityId(candidate))) {
return candidate;
}
}
throw new IllegalStateException("No relying party with Entity ID '" + requiredEntityId + "' found");
}
private Object getEntityId(RelyingPartyRegistration.Builder candidate) {
String[] result = new String[1];
candidate.assertingPartyMetadata((builder) -> result[0] = builder.build().getEntityId());
return result[0];
}
private Consumer<AssertingPartyMetadata.Builder<?>> mapAssertingParty(AssertingParty assertingParty) {
return (details) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(assertingParty::getEntityId).to(details::entityId);
map.from(assertingParty.getSinglesignon()::getBinding).to(details::singleSignOnServiceBinding);
map.from(assertingParty.getSinglesignon()::getUrl).to(details::singleSignOnServiceLocation);
map.from(assertingParty.getSinglesignon()::getSignRequest).to(details::wantAuthnRequestsSigned);
map.from(assertingParty.getSinglelogout()::getUrl).to(details::singleLogoutServiceLocation);
map.from(assertingParty.getSinglelogout()::getResponseUrl).to(details::singleLogoutServiceResponseLocation);
map.from(assertingParty.getSinglelogout()::getBinding).to(details::singleLogoutServiceBinding);
};
}
private void validateSigningCredentials(Registration properties, boolean signRequest) {
if (signRequest) {
Assert.state(!properties.getSigning().getCredentials().isEmpty(),
"Signing credentials must not be empty when authentication requests require signing.");
}
}
private Saml2X509Credential asSigningCredential(Signing.Credential properties) {
RSAPrivateKey privateKey = readPrivateKey(properties.getPrivateKeyLocation());
X509Certificate certificate = readCertificate(properties.getCertificateLocation());
return new Saml2X509Credential(privateKey, certificate, Saml2X509CredentialType.SIGNING);
}
private Saml2X509Credential asDecryptionCredential(Decryption.Credential properties) {
RSAPrivateKey privateKey = readPrivateKey(properties.getPrivateKeyLocation());
X509Certificate certificate = readCertificate(properties.getCertificateLocation());
return new Saml2X509Credential(privateKey, certificate, Saml2X509CredentialType.DECRYPTION);
}
private Saml2X509Credential asVerificationCredential(Verification.Credential properties) {
X509Certificate certificate = readCertificate(properties.getCertificateLocation());
return new Saml2X509Credential(certificate, Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION,
Saml2X509Credential.Saml2X509CredentialType.VERIFICATION);
}
private RSAPrivateKey readPrivateKey(Resource location) {
Assert.state(location != null, "No private key location specified");
Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist");
try (InputStream inputStream = location.getInputStream()) {
PemContent pemContent = PemContent.load(inputStream);
PrivateKey privateKey = pemContent.getPrivateKey();
Assert.state(privateKey instanceof RSAPrivateKey,
() -> "PrivateKey in resource '" + location + "' must be an RSAPrivateKey");
return (RSAPrivateKey) privateKey;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
private X509Certificate readCertificate(Resource location) {
Assert.state(location != null, "No certificate location specified");
Assert.state(location.exists(), () -> "Certificate location '" + location + "' does not exist");
try (InputStream inputStream = location.getInputStream()) {
PemContent pemContent = PemContent.load(inputStream);
List<X509Certificate> certificates = pemContent.getCertificates();
return certificates.get(0);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Security's SAML 2.0.
*/
package org.springframework.boot.security.autoconfigure.saml2;

View File

@@ -1,7 +1,6 @@
org.springframework.boot.security.autoconfigure.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.security.autoconfigure.reactive.ReactiveUserDetailsServiceAutoConfiguration
org.springframework.boot.security.autoconfigure.rsocket.RSocketSecurityAutoConfiguration
org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyAutoConfiguration
org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration
org.springframework.boot.security.autoconfigure.servlet.SecurityFilterAutoConfiguration
org.springframework.boot.security.autoconfigure.servlet.UserDetailsServiceAutoConfiguration

View File

@@ -1,442 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.io.InputStream;
import java.util.List;
import jakarta.servlet.Filter;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.filter.CompositeFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link Saml2RelyingPartyAutoConfiguration}.
*
* @author Madhura Bhave
* @author Moritz Halbritter
* @author Lasse Lindqvist
* @author Scott Frederick
*/
class Saml2RelyingPartyAutoConfigurationTests {
private static final String PREFIX = "spring.security.saml2.relyingparty.registration";
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration(
AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class, SecurityAutoConfiguration.class));
@Test
void autoConfigurationShouldBeConditionalOnRelyingPartyRegistrationRepositoryClass() {
this.contextRunner.withPropertyValues(getPropertyValues())
.withClassLoader(new FilteredClassLoader(
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"))
.run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class));
}
@Test
void autoConfigurationShouldBeConditionalOnServletWebApplication() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class))
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class));
}
@Test
void relyingPartyRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class));
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
this.contextRunner.withPropertyValues(getPropertyValues()).run((context) -> {
RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php");
assertThat(registration.getAssertingPartyMetadata().getEntityId())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
assertThat(registration.getAssertionConsumerServiceLocation())
.isEqualTo("{baseUrl}/login/saml2/foo-entity-id");
assertThat(registration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
assertThat(registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()).isFalse();
assertThat(registration.getSigningX509Credentials()).hasSize(1);
assertThat(registration.getDecryptionX509Credentials()).hasSize(1);
assertThat(registration.getAssertingPartyMetadata().getVerificationX509Credentials()).isNotNull();
assertThat(registration.getEntityId()).isEqualTo("{baseUrl}/saml2/foo-entity-id");
assertThat(registration.getSingleLogoutServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php");
assertThat(registration.getSingleLogoutServiceResponseLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/");
assertThat(registration.getSingleLogoutServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php");
assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/");
assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
});
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void autoConfigurationWhenSignRequestsTrueAndNoSigningCredentialsShouldThrowException() {
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(true)).run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasMessageContaining(
"Signing credentials must not be empty when authentication requests require signing.");
});
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrowException() {
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false))
.run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class));
}
@Test
@WithPackageResources("idp-metadata")
void autoconfigurationShouldQueryAssertingPartyMetadataWhenMetadataUrlIsPresent() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl)
.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(server.getRequestCount()).isOne();
});
}
}
@Test
@WithPackageResources("idp-metadata")
void autoconfigurationShouldUseBindingFromMetadataUrlIfPresent() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl)
.run((context) -> {
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
});
}
}
@Test
@WithPackageResources("idp-metadata")
void autoconfigurationWhenMetadataUrlAndPropertyPresentShouldUseBindingFromProperty() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner
.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl,
PREFIX + ".foo.assertingparty.singlesignon.binding=redirect")
.run((context) -> {
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.REDIRECT);
});
}
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void autoconfigurationWhenNoMetadataUrlOrPropertyPresentShouldUseRedirectBinding() {
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSsoBinding()).run((context) -> {
RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.REDIRECT);
});
}
@Test
void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() {
this.contextRunner.withPropertyValues(getPropertyValues())
.withUserConfiguration(RegistrationRepositoryConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(context).hasBean("testRegistrationRepository");
});
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void samlLoginShouldBeConfigured() {
this.contextRunner.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasSecurityFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue());
}
@Test
@WithPackageResources({ "private-key-location", "certificate-location" })
void samlLoginShouldBackOffWhenASecurityFilterChainBeanIsPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class))
.withUserConfiguration(TestSecurityFilterChainConfig.class)
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasSecurityFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse());
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void samlLoginShouldShouldBeConditionalOnSecurityWebFilterClass() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(Thread.currentThread().getContextClassLoader(), SecurityFilterChain.class))
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(context).doesNotHaveBean(SecurityFilterChain.class));
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void samlLogoutShouldBeConfigured() {
this.contextRunner.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasSecurityFilter(context, Saml2LogoutRequestFilter.class)).isTrue());
}
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) {
return new String[] { PREFIX
+ ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=" + signRequests,
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location" };
}
@Test
@WithPackageResources("idp-metadata-with-multiple-providers")
void autoconfigurationWhenMultipleProvidersAndNoSpecifiedEntityId() throws Exception {
testMultipleProviders(null, "https://idp.example.com/idp/shibboleth");
}
@Test
@WithPackageResources("idp-metadata-with-multiple-providers")
void autoconfigurationWhenMultipleProvidersAndSpecifiedEntityId() throws Exception {
testMultipleProviders("https://idp.example.com/idp/shibboleth", "https://idp.example.com/idp/shibboleth");
testMultipleProviders("https://idp2.example.com/idp/shibboleth", "https://idp2.example.com/idp/shibboleth");
}
@Test
@WithPackageResources("idp-metadata")
void signRequestShouldApplyIfMetadataUriIsSet() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl,
PREFIX + ".foo.assertingparty.singlesignon.sign-request=true",
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:org/springframework/boot/security/autoconfigure/saml2/rsa.key",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:org/springframework/boot/security/autoconfigure/saml2/rsa.crt")
.run((context) -> {
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()).isTrue();
});
}
}
@Test
@WithPackageResources("certificate-location")
void autoconfigurationWithInvalidPrivateKeyShouldFail() {
this.contextRunner.withPropertyValues(
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:certificate-location",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.rootCause()
.hasMessageContaining("Missing private key or unrecognized format"));
}
@Test
@WithPackageResources("private-key-location")
void autoconfigurationWithInvalidCertificateShouldFail() {
this.contextRunner.withPropertyValues(
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:private-key-location",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:private-key-location",
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:private-key-location")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.rootCause()
.hasMessageContaining("Missing certificates or unrecognized format"));
}
private void testMultipleProviders(String specifiedEntityId, String expected) throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata-with-multiple-providers"));
WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl);
if (specifiedEntityId != null) {
contextRunner = contextRunner
.withPropertyValues(PREFIX + ".foo.assertingparty.entity-id=" + specifiedEntityId);
}
contextRunner.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(server.getRequestCount()).isOne();
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getEntityId()).isEqualTo(expected);
});
}
}
private String[] getPropertyValuesWithoutSsoBinding() {
return new String[] { PREFIX
+ ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location" };
}
private String[] getPropertyValues() {
return new String[] {
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:private-key-location",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.decryption.credentials[0].private-key-location=classpath:private-key-location",
PREFIX + ".foo.decryption.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.singlelogout.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php",
PREFIX + ".foo.singlelogout.response-url=https://simplesaml-for-spring-saml.cfapps.io/",
PREFIX + ".foo.singlelogout.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.asserting-party.singlelogout.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php",
PREFIX + ".foo.asserting-party.singlelogout.response-url=https://simplesaml-for-spring-saml.cfapps.io/",
PREFIX + ".foo.asserting-party.singlelogout.binding=post",
PREFIX + ".foo.entity-id={baseUrl}/saml2/foo-entity-id",
PREFIX + ".foo.acs.location={baseUrl}/login/saml2/foo-entity-id",
PREFIX + ".foo.acs.binding=redirect" };
}
private boolean hasSecurityFilter(AssertableWebApplicationContext context, Class<? extends Filter> filter) {
return getSecurityFilterChain(context).getFilters().stream().anyMatch(filter::isInstance);
}
private SecurityFilterChain getSecurityFilterChain(AssertableWebApplicationContext context) {
Filter springSecurityFilterChain = context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class);
FilterChainProxy filterChainProxy = getFilterChainProxy(springSecurityFilterChain);
SecurityFilterChain securityFilterChain = filterChainProxy.getFilterChains().get(0);
return securityFilterChain;
}
private FilterChainProxy getFilterChainProxy(Filter filter) {
if (filter instanceof FilterChainProxy filterChainProxy) {
return filterChainProxy;
}
if (filter instanceof CompositeFilter) {
List<?> filters = (List<?>) ReflectionTestUtils.getField(filter, "filters");
return (FilterChainProxy) filters.stream()
.filter(FilterChainProxy.class::isInstance)
.findFirst()
.orElseThrow();
}
throw new IllegalStateException("No FilterChainProxy found");
}
private void setupMockResponse(MockWebServer server, Resource resourceBody) throws Exception {
try (InputStream metadataSource = resourceBody.getInputStream()) {
try (Buffer metadataBuffer = new Buffer()) {
metadataBuffer.readFrom(metadataSource);
MockResponse metadataResponse = new MockResponse().setBody(metadataBuffer);
server.enqueue(metadataResponse);
}
}
}
@Configuration(proxyBeanMethods = false)
static class RegistrationRepositoryConfiguration {
@Bean
RelyingPartyRegistrationRepository testRegistrationRepository() {
return mock(RelyingPartyRegistrationRepository.class);
}
}
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.securityMatcher("/**")
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Saml2RelyingPartyProperties}.
*
* @author Madhura Bhave
* @author Lasse Wulff
*/
class Saml2RelyingPartyPropertiesTests {
private final Saml2RelyingPartyProperties properties = new Saml2RelyingPartyProperties();
@Test
void customizeSsoUrl() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.single-sign-on.url",
"https://simplesaml-for-spring-saml/SSOService.php");
assertThat(
this.properties.getRegistration().get("simplesamlphp").getAssertingparty().getSinglesignon().getUrl())
.isEqualTo("https://simplesaml-for-spring-saml/SSOService.php");
}
@Test
void customizeSsoBinding() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.single-sign-on.binding",
"post");
assertThat(this.properties.getRegistration()
.get("simplesamlphp")
.getAssertingparty()
.getSinglesignon()
.getBinding()).isEqualTo(Saml2MessageBinding.POST);
}
@Test
void customizeSsoSignRequests() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.single-sign-on.sign-request",
"false");
assertThat(this.properties.getRegistration()
.get("simplesamlphp")
.getAssertingparty()
.getSinglesignon()
.getSignRequest()).isFalse();
}
@Test
void customizeRelyingPartyEntityId() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.entity-id",
"{baseUrl}/saml2/custom-entity-id");
assertThat(this.properties.getRegistration().get("simplesamlphp").getEntityId())
.isEqualTo("{baseUrl}/saml2/custom-entity-id");
}
@Test
void customizeRelyingPartyEntityIdDefaultsToServiceProviderMetadata() {
assertThat(RelyingPartyRegistration.withRegistrationId("id")).extracting("entityId")
.isEqualTo(new Saml2RelyingPartyProperties.Registration().getEntityId());
}
@Test
void customizeAssertingPartyMetadataUri() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.metadata-uri",
"https://idp.example.org/metadata");
assertThat(this.properties.getRegistration().get("simplesamlphp").getAssertingparty().getMetadataUri())
.isEqualTo("https://idp.example.org/metadata");
}
@Test
void customizeSsoSignRequestsIsNullByDefault() {
this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration());
assertThat(this.properties.getRegistration()
.get("simplesamlphp")
.getAssertingparty()
.getSinglesignon()
.getSignRequest()).isNull();
}
@Test
void customizeNameIdFormat() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.name-id-format", "sampleNameIdFormat");
assertThat(this.properties.getRegistration().get("simplesamlphp").getNameIdFormat())
.isEqualTo("sampleNameIdFormat");
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("spring.security.saml2.relyingparty", Bindable.ofInstance(this.properties));
}
}

View File

@@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD
VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX
c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw
aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ
BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa
BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD
DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr
QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62
E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz
2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW
RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ
nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5
cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph
iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5
ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO
nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v
ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu
xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z
V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3
lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk
-----END CERTIFICATE-----

View File

@@ -1,42 +0,0 @@
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>

View File

@@ -1,86 +0,0 @@
<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="virtu-20230614094100" Name="virtu" validUntil="2023-07-12T06:41:00Z" xsi:schemaLocation="urn:oasis:names:tc:SAML:2.0:metadata saml-schema-metadata-2.0.xsd http://www.w3.org/2000/09/xmldsig# xmldsig-core-schema.xsd">
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
<md:EntityDescriptor entityID="https://idp2.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp2.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact2@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
</EntitiesDescriptor>

View File

@@ -1,16 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE
VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK
cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6
Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn
x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5
wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd
vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY
8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX
oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx
EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0
KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt
YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr
9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM
INrtuLp4YHbgk1mi
-----END PRIVATE KEY-----

View File

@@ -1,23 +0,0 @@
-----BEGIN CERTIFICATE-----
MIID1zCCAr+gAwIBAgIUCzQeKBMTO0iHVW3iKmZC41haqCowDQYJKoZIhvcNAQEL
BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI
Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55
U2VjdGlvbk5hbWUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjAwODI5MDNa
Fw0zMzA5MTcwODI5MDNaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5h
bWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkG
A1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUfi4aaCotJZX6OSDjv6fxCCfc
ihSs91Z/mmN+yc1fsxVSs53SIbqUuo+Wzhv34kp8I/r03P9LWVTkFPbeDxAl75Oa
PGggxK55US0Zfy9Hj1BwWIKV3330N61emID1GDEtFKL4yJbJdreQXnIXTBL2o76V
nuV/tYozyZnb07IQ1WhUm5WDxgzM0yFudMynTczCBeZHfvharDtB8PFFhCZXW2/9
TZVVfW4oOML8EAX3hvnvYBlFl/foxXekZSwq/odOkmWCZavT2+0sburHUlOnPGUh
Qj4tHwpMRczp7VX4ptV1D2UrxsK/2B+s9FK2QSLKQ9JzAYJ6WxQjHcvET9jvAgMB
AAGjUzBRMB0GA1UdDgQWBBQjDr/1E/01pfLPD8uWF7gbaYL0TTAfBgNVHSMEGDAW
gBQjDr/1E/01pfLPD8uWF7gbaYL0TTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQAGjUuec0+0XNMCRDKZslbImdCAVsKsEWk6NpnUViDFAxL+KQuC
NW131UeHb9SCzMqRwrY4QI3nAwJQCmilL/hFM3ss4acn3WHu1yci/iKPUKeL1ec5
kCFUmqX1NpTiVaytZ/9TKEr69SMVqNfQiuW5U1bIIYTqK8xo46WpM6YNNHO3eJK6
NH0MW79Wx5ryi4i4C6afqYbVbx7tqcmy8CFeNxgZ0bFQ87SiwYXIj77b6sVYbu32
doykBQgSHLcagWASPQ73m73CWUgo+7+EqSKIQqORbgmTLPmOUh99gFIx7jmjTyHm
NBszx1ZVWuIv3mWmp626Kncyc+LLM9tvgymx
-----END CERTIFICATE-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUfi4aaCotJZX6
OSDjv6fxCCfcihSs91Z/mmN+yc1fsxVSs53SIbqUuo+Wzhv34kp8I/r03P9LWVTk
FPbeDxAl75OaPGggxK55US0Zfy9Hj1BwWIKV3330N61emID1GDEtFKL4yJbJdreQ
XnIXTBL2o76VnuV/tYozyZnb07IQ1WhUm5WDxgzM0yFudMynTczCBeZHfvharDtB
8PFFhCZXW2/9TZVVfW4oOML8EAX3hvnvYBlFl/foxXekZSwq/odOkmWCZavT2+0s
burHUlOnPGUhQj4tHwpMRczp7VX4ptV1D2UrxsK/2B+s9FK2QSLKQ9JzAYJ6WxQj
HcvET9jvAgMBAAECggEADdeRuZml1F65mDJm1enduaH+NWvEm1yEr3ecr0fbujYI
bQ89+CVx/znvRvPH4aFwQwmgUZl12JrfS05MTectoPMBf/obDwtmPDPmsV2rdEi9
2jEB11vW23T8X7L6hOdzCKHqrd8kkhzK1LuPnhHlaFipU8YlOBOuMYpv8eB78y79
Qkd5/ZEygFhqVGz96R7nT/xS21aPC7OPhicAauLLuguF4caCNhwkjLi3bizLemUn
4i41q69drg7G8WX6BTxzem5FupKfI8rn2EkOjO/biVRknzGxAdqkM8SDHWkqeOuY
8QVhc1kZsMkB0BGPlDPStUwEHSfUiND4GJTcngc++QKBgQD2lyeW3PoPjQ1qzjN4
V/0XE77zpcPE5dW7chLtiWRY1dqk2uOJ32iOtxuqk9Q/YMSZyPJlTkfI5JePuC/B
MB+QXzXuWN03Vn0ZrOpQlxcdA4A1o10NT1nEw8kZlf4+LyUk8GpMGUhjnxFZpZbf
5S3fy0/2V8wGvOmXR65c8m6ASQKBgQDcmfCV5npu1HrtO8jmU9gBIhniNjB4IWue
TSRt3ANDQaVBqsVaIMe/mUEQrZ6MdikMeA4bobOA6bUYwOiq8JGWSenAzGL22TbA
W51q6A8hgDCuH1JnoagqUIbr61kwEVcfbRHEFpuxLURsjoDg/xBtwO96SxWPh5Wr
+f1q8t5/dwKBgGWc+AVk3e6Wk1bVzcPjjjl6O4+vWTLD+wUZBs+3dBBfX4/bWzQv
Sai1r8Lk0+uh9qHgenJghZg1CneA0LztFbSqZ1DmcZIiI7720D+RY0bjcGup++hG
MJmyjCXs9y2sw8OrBkKBkKDspXupjriIehTkdPjwSPTl1+Qs9575j6txAoGAT8n+
ErnCHsQLkjLFf0lkH0TOR9uBvHGaEy+jtXiWVYUw2IeDyg2BMfOkbPvfFL7IKhJi
R+w8mKvvLHzZqrpIbitduLY0NURrYTfBwCEfF+bdtJzvmTwHLwbhRgNhxtj+wgcZ
HetvdK4CyaDhTH/02T2nYHw32CoaIJHS7xPZFhECgYEAv7xRawjlrC4V0BLjP3Ej
pk8BbsRABxN1CrS6nJK+So4u2gKQDsL3WA0oJTS8v8AD5LvQUNr1d57FVlq9lwCd
u623eOIuluCUZBVy1iYdkRXWz9pg5bCidCgEYUpF3SqpsuFou0XFzDD773UVQFVw
VYriYasPwmzS2y2P7PKFzJs=
-----END PRIVATE KEY-----