Create spring-boot-security-saml2 module
This commit is contained in:
committed by
Phillip Webb
parent
4d8b558b9b
commit
4ce8b591e0
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
Reference in New Issue
Block a user