19 changed files with 889 additions and 0 deletions
@ -0,0 +1,59 @@
@@ -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<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()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,45 @@
@@ -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(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,44 @@
@@ -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 { |
||||
|
||||
} |
||||
@ -0,0 +1,182 @@
@@ -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<String, Registration> registration = new LinkedHashMap<>(); |
||||
|
||||
public Map<String, Registration> 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<Credential> credentials = new ArrayList<>(); |
||||
|
||||
public List<Credential> 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<Credential> credentials = new ArrayList<>(); |
||||
|
||||
public List<Credential> 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,121 @@
@@ -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<RelyingPartyRegistration> registrations = properties.getRegistration().entrySet().stream() |
||||
.map(this::asRegistration).collect(Collectors.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) { |
||||
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<Saml2X509Credential> asCredentials(Registration properties) { |
||||
List<Saml2X509Credential> 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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,133 @@
@@ -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<? extends Filter> filter) { |
||||
FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); |
||||
List<SecurityFilterChain> filterChains = filterChain.getFilterChains(); |
||||
List<Filter> filters = filterChains.get(0).getFilters(); |
||||
return filters.stream().anyMatch(filter::isInstance); |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
static class RegistrationRepositoryConfiguration { |
||||
|
||||
@Bean |
||||
RelyingPartyRegistrationRepository testRegistrationRepository() { |
||||
return mock(RelyingPartyRegistrationRepository.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,24 @@
@@ -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----- |
||||
@ -0,0 +1,16 @@
@@ -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----- |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<parent> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-smoke-tests</artifactId> |
||||
<version>${revision}</version> |
||||
</parent> |
||||
<artifactId>spring-boot-smoke-test-saml2-service-provider</artifactId> |
||||
<name>Spring Boot SAML2 Service Provider Smoke Test</name> |
||||
<description>Spring Boot SAML2 Service Provider Smoke Test</description> |
||||
<properties> |
||||
<main.basedir>${basedir}/../../..</main.basedir> |
||||
</properties> |
||||
<dependencies> |
||||
<!-- Compile --> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-web</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-config</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-saml2-service-provider</artifactId> |
||||
</dependency> |
||||
<!-- Test --> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
||||
@ -0,0 +1,32 @@
@@ -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(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@
@@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,27 @@
@@ -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 |
||||
@ -0,0 +1,24 @@
@@ -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----- |
||||
@ -0,0 +1,16 @@
@@ -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----- |
||||
@ -0,0 +1,56 @@
@@ -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<String> 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<String> 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"); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue