From 22ed56ac52e41fcf595b0eda8afaf2027e6e0e4f Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 1 Oct 2019 13:22:25 -0700 Subject: [PATCH] Add auto-config for Spring Security's SAML support Closes gh-18260 Co-authored-by: Phillip Webb --- .../spring-boot-autoconfigure/pom.xml | 5 + .../RegistrationConfiguredCondition.java | 59 ++++++ .../saml2/Saml2LoginConfiguration.java | 45 +++++ .../Saml2RelyingPartyAutoConfiguration.java | 44 +++++ .../saml2/Saml2RelyingPartyProperties.java | 182 ++++++++++++++++++ ...RelyingPartyRegistrationConfiguration.java | 121 ++++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...ml2RelyingPartyAutoConfigurationTests.java | 133 +++++++++++++ .../test/resources/saml/certificate-location | 24 +++ .../test/resources/saml/private-key-location | 16 ++ .../main/asciidoc/spring-boot-features.adoc | 29 +++ .../spring-boot-smoke-tests/pom.xml | 1 + .../pom.xml | 45 +++++ .../serviceprovider/ExampleController.java | 32 +++ .../SampleSaml2RelyingPartyApplication.java | 29 +++ .../src/main/resources/application.yml | 27 +++ .../src/main/resources/saml/certificate.txt | 24 +++ .../src/main/resources/saml/privatekey.txt | 16 ++ ...mpleSaml2RelyingPartyApplicationTests.java | 56 ++++++ 19 files changed, 889 insertions(+) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/RegistrationConfiguredCondition.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2LoginConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/certificate-location create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/private-key-location create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/pom.xml create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/ExampleController.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplication.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/certificate.txt create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/privatekey.txt create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index 8b56df8a82b..d9cda412802 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -682,6 +682,11 @@ spring-security-oauth2-resource-server true + + org.springframework.security + spring-security-saml2-service-provider + true + org.springframework.security spring-security-web diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/RegistrationConfiguredCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/RegistrationConfiguredCondition.java new file mode 100644 index 00000000000..616767bb9dc --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/RegistrationConfiguredCondition.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.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.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +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> 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 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 getRegistrations(Environment environment) { + return Binder.get(environment).bind(PROPERTY, STRING_REGISTRATION_MAP).orElse(Collections.emptyMap()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2LoginConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2LoginConfiguration.java new file mode 100644 index 00000000000..c0aafc9fa9a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2LoginConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.saml2; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; + +/** + * {@link WebSecurityConfigurerAdapter} configuration for Spring Security's relying party + * SAML support. + * + * @author Madhura Bhave + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnBean(RelyingPartyRegistrationRepository.class) +class Saml2LoginConfiguration { + + @Configuration(proxyBeanMethods = false) + static class Saml2LoginConfigurerAdapter extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests((requests) -> requests.anyRequest().authenticated()).saml2Login(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfiguration.java new file mode 100644 index 00000000000..b44fb56115e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.saml2; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +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 2.2.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(RelyingPartyRegistrationRepository.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@Import({ Saml2RelyingPartyRegistrationConfiguration.class, Saml2LoginConfiguration.class }) +@AutoConfigureBefore(SecurityAutoConfiguration.class) +@EnableConfigurationProperties(Saml2RelyingPartyProperties.class) +public class Saml2RelyingPartyAutoConfiguration { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java new file mode 100644 index 00000000000..6a445e2ec94 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java @@ -0,0 +1,182 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.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; + +/** + * SAML2 relying party properties. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.2.0 + */ +@ConfigurationProperties("spring.security.saml2.relyingparty") +public class Saml2RelyingPartyProperties { + + /** + * SAML2 relying party registrations. + */ + private Map registration = new LinkedHashMap<>(); + + public Map getRegistration() { + return this.registration; + } + + /** + * Represents a SAML Relying Party. + */ + public static class Registration { + + private final Signing signing = new Signing(); + + /** + * Remote SAML Identity Provider. + */ + private Identityprovider identityprovider = new Identityprovider(); + + public Signing getSigning() { + return this.signing; + } + + Identityprovider getIdentityprovider() { + return this.identityprovider; + } + + public static class Signing { + + /** + * Credentials used for signing and decrypting the SAML authentication + * request. + */ + private List credentials = new ArrayList<>(); + + public List getCredentials() { + return this.credentials; + } + + public static class Credential { + + /** + * Private key used for signing or 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 Identityprovider { + + /** + * Unique identifier for the identity provider. + */ + private String entityId; + + /** + * Remote endpoint to send authentication requests to. + */ + private String ssoUrl; + + private Verification verification = new Verification(); + + public String getEntityId() { + return this.entityId; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + + public String getSsoUrl() { + return this.ssoUrl; + } + + public void setSsoUrl(String ssoUrl) { + this.ssoUrl = ssoUrl; + } + + public Verification getVerification() { + return this.verification; + } + + public static class Verification { + + /** + * Credentials used for verification of incoming SAML messages. + */ + private List credentials = new ArrayList<>(); + + public List getCredentials() { + return this.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; + } + + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java new file mode 100644 index 00000000000..a514c2ad36c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java @@ -0,0 +1,121 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.saml2; + +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Identityprovider.Verification; +import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration; +import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration.Signing; +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.converter.RsaKeyConverters; +import org.springframework.security.saml2.credentials.Saml2X509Credential; +import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType; +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.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter; +import org.springframework.util.Assert; + +/** + * {@link Configuration @Configuration} used to map {@link Saml2RelyingPartyProperties} to + * relying party registrations in a {@link RelyingPartyRegistrationRepository}. + * + * @author Madhura Bhave + * @author Phillip Webb + */ +@Configuration(proxyBeanMethods = false) +@Conditional(RegistrationConfiguredCondition.class) +@ConditionalOnMissingBean(RelyingPartyRegistrationRepository.class) +class Saml2RelyingPartyRegistrationConfiguration { + + @Bean + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(Saml2RelyingPartyProperties properties) { + List registrations = properties.getRegistration().entrySet().stream() + .map(this::asRegistration).collect(Collectors.toList()); + return new InMemoryRelyingPartyRegistrationRepository(registrations); + } + + private RelyingPartyRegistration asRegistration(Map.Entry entry) { + return asRegistration(entry.getKey(), entry.getValue()); + } + + private RelyingPartyRegistration asRegistration(String id, Registration properties) { + RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(id); + builder.assertionConsumerServiceUrlTemplate( + "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + builder.idpWebSsoUrl(properties.getIdentityprovider().getSsoUrl()); + builder.remoteIdpEntityId(properties.getIdentityprovider().getEntityId()); + builder.credentials((credentials) -> credentials.addAll(asCredentials(properties))); + return builder.build(); + } + + private List asCredentials(Registration properties) { + List credentials = new ArrayList<>(); + properties.getSigning().getCredentials().stream().map(this::asSigningCredential).forEach(credentials::add); + properties.getIdentityprovider().getVerification().getCredentials().stream().map(this::asVerificationCredential) + .forEach(credentials::add); + return credentials; + } + + private Saml2X509Credential asSigningCredential(Signing.Credential properties) { + RSAPrivateKey privateKey = readPrivateKey(properties.getPrivateKeyLocation()); + X509Certificate certificate = readCertificate(properties.getCertificateLocation()); + return new Saml2X509Credential(privateKey, certificate, Saml2X509CredentialType.SIGNING, + Saml2X509CredentialType.DECRYPTION); + } + + private Saml2X509Credential asVerificationCredential(Verification.Credential properties) { + X509Certificate certificate = readCertificate(properties.getCertificateLocation()); + return new Saml2X509Credential(certificate, Saml2X509CredentialType.ENCRYPTION, + 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()) { + return RsaKeyConverters.pkcs8().convert(inputStream); + } + 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()) { + return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream); + } + catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index eb889e83103..55a14896416 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -109,6 +109,7 @@ org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoCo org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\ +org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java new file mode 100644 index 00000000000..b30c48da4aa --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.saml2; + +import java.util.List; + +import javax.servlet.Filter; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.security.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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.BeanIds; +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.servlet.filter.Saml2WebSsoAuthenticationFilter; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link Saml2RelyingPartyAutoConfiguration}. + * + * @author Madhura Bhave + */ +public class Saml2RelyingPartyAutoConfigurationTests { + + private static final String PREFIX = "spring.security.saml2.relyingparty.registration"; + + private 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 + void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() { + this.contextRunner.withPropertyValues(getPropertyValues()).run((context) -> { + RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class); + RelyingPartyRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration.getIdpWebSsoUrl()) + .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"); + assertThat(registration.getRemoteIdpEntityId()) + .isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"); + assertThat(registration.getAssertionConsumerServiceUrlTemplate()) + .isEqualTo("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + assertThat(registration.getSigningCredentials()).isNotNull(); + assertThat(registration.getVerificationCredentials()).isNotNull(); + }); + } + + @Test + void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() { + this.contextRunner.withPropertyValues(getPropertyValues()) + .withUserConfiguration(RegistrationRepositoryConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class); + assertThat(context).hasBean("testRegistrationRepository"); + }); + } + + @Test + void samlLoginShouldBeConfigured() { + this.contextRunner.withPropertyValues(getPropertyValues()) + .run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue()); + } + + private String[] getPropertyValues() { + return new String[] { + PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/private-key-location", + PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/certificate-location", + PREFIX + ".foo.identityprovider.sso-url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + PREFIX + ".foo.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php", + PREFIX + ".foo.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location" }; + } + + private boolean hasFilter(AssertableWebApplicationContext context, Class filter) { + FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); + List filterChains = filterChain.getFilterChains(); + List filters = filterChains.get(0).getFilters(); + return filters.stream().anyMatch(filter::isInstance); + } + + @Configuration(proxyBeanMethods = false) + static class RegistrationRepositoryConfiguration { + + @Bean + RelyingPartyRegistrationRepository testRegistrationRepository() { + return mock(RelyingPartyRegistrationRepository.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/certificate-location b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/certificate-location new file mode 100644 index 00000000000..c04a9c1602f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/certificate-location @@ -0,0 +1,24 @@ +-----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----- \ No newline at end of file diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/private-key-location b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/private-key-location new file mode 100644 index 00000000000..c9db80095a8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/saml/private-key-location @@ -0,0 +1,16 @@ +-----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----- \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 75cd2ea30bc..3738bd79ecc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3239,6 +3239,35 @@ However, this functionality is available from the {spring-security-oauth2}[Sprin Until then, you can use the `spring-security-oauth2-autoconfigure` module to easily set up an OAuth 2.0 authorization server; see its https://docs.spring.io/spring-security-oauth2-boot[documentation] for instructions. +[[boot-features-security-saml]] +=== SAML 2.0 + + + +[[boot-features-security-saml2-relying-party]] +==== Relying Party +If you have `spring-security-saml2-service-provider` on your classpath, you can take advantage of some auto-configuration to make it easy to set up a SAML 2.0 Relying Party. +This configuration makes use of the properties under `Saml2RelyingPartyProperties`. + +A relying party registration represents a paired configuration between an Identity Provider, IDP, and a Service Provider, SP. +You can register multiple relying parties under the `spring.security.saml2.relyingparty` prefix, as shown in the following example: + +[source,properties,indent=0,configprops] +---- + spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key + spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate + spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.verification.credentials[0].certificate-location=path-to-verification-cert + spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.entity-id=remote-idp-entity-id1 + spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.sso-url=https://remoteidp1.sso.url + + spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key + spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate + spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.verification.credentials[0].certificate-location=path-to-other-verification-cert + spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.entity-id=remote-idp-entity-id2 + spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.sso-url=https://remoteidp2.sso.url +---- + + [[boot-features-security-actuator]] === Actuator Security diff --git a/spring-boot-tests/spring-boot-smoke-tests/pom.xml b/spring-boot-tests/spring-boot-smoke-tests/pom.xml index 03ef3488177..10de667638f 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/pom.xml +++ b/spring-boot-tests/spring-boot-smoke-tests/pom.xml @@ -71,6 +71,7 @@ spring-boot-smoke-test-reactive-oauth2-client spring-boot-smoke-test-reactive-oauth2-resource-server spring-boot-smoke-test-rsocket + spring-boot-smoke-test-saml2-service-provider spring-boot-smoke-test-secure spring-boot-smoke-test-secure-jersey spring-boot-smoke-test-secure-webflux diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/pom.xml b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/pom.xml new file mode 100644 index 00000000000..a09b044368b --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-smoke-tests + ${revision} + + spring-boot-smoke-test-saml2-service-provider + Spring Boot SAML2 Service Provider Smoke Test + Spring Boot SAML2 Service Provider Smoke Test + + ${basedir}/../../.. + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-saml2-service-provider + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/ExampleController.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/ExampleController.java new file mode 100644 index 00000000000..fe511ed3a76 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/ExampleController.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2019 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 smoketest.saml2.serviceprovider; + +import java.security.Principal; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ExampleController { + + @RequestMapping("/") + public String email(Principal principal) { + return "Hello " + principal.getName(); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplication.java new file mode 100644 index 00000000000..1fc76c228c2 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2019 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 smoketest.saml2.serviceprovider; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleSaml2RelyingPartyApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleSaml2RelyingPartyApplication.class); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml new file mode 100644 index 00000000000..98303152f1d --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + security: + saml2: + relyingparty: + registration: + simplesamlphp: + signing: + credentials: + - private-key-location: "classpath:saml/privatekey.txt" + certificate-location: "classpath:saml/certificate.txt" + identityprovider: + verification: + credentials: + - certificate-location: "classpath:saml/certificate.txt" + entity-id: simplesaml + sso-url: https://simplesaml-for-spring-saml/SSOService.php + okta: + signing: + credentials: + - private-key-location: "classpath:saml/privatekey.txt" + certificate-location: "classpath:saml/certificate.txt" + identityprovider: + verification: + credentials: + - certificate-location: "classpath:saml/certificate.txt" + entity-id: okta-id-1234 + sso-url: https://okta-for-spring/saml2/idp/SSOService.php diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/certificate.txt b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/certificate.txt new file mode 100644 index 00000000000..c04a9c1602f --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/certificate.txt @@ -0,0 +1,24 @@ +-----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----- \ No newline at end of file diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/privatekey.txt b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/privatekey.txt new file mode 100644 index 00000000000..c9db80095a8 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/saml/privatekey.txt @@ -0,0 +1,16 @@ +-----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----- \ No newline at end of file diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java new file mode 100644 index 00000000000..b38cd2896b6 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2019 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 smoketest.saml2.serviceprovider; + +import java.net.URI; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class SampleSaml2RelyingPartyApplicationTests { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void everythingShouldRedirectToLogin() { + ResponseEntity entity = this.restTemplate.getForEntity("/", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(entity.getHeaders().getLocation()).isEqualTo(URI.create("http://localhost:" + this.port + "/login")); + } + + @Test + void loginShouldHaveAllIdentityProvidersToChooseFrom() { + ResponseEntity entity = this.restTemplate.getForEntity("/login", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).contains("/saml2/authenticate/simplesamlphp"); + assertThat(entity.getBody()).contains("/saml2/authenticate/okta"); + } + +}