Add configuration options for SAML authentication requests
Closes gh-20584
This commit is contained in:
parent
6a0d620860
commit
8659102650
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -23,6 +23,7 @@ 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.
|
||||
|
|
@ -124,6 +125,8 @@ public class Saml2RelyingPartyProperties {
|
|||
*/
|
||||
private String ssoUrl;
|
||||
|
||||
private SingleSignOn singleSignOn = new SingleSignOn();
|
||||
|
||||
private Verification verification = new Verification();
|
||||
|
||||
public String getEntityId() {
|
||||
|
|
@ -134,18 +137,67 @@ public class Saml2RelyingPartyProperties {
|
|||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getSsoUrl() {
|
||||
return this.ssoUrl;
|
||||
return this.getSingleSignOn().getUrl();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setSsoUrl(String ssoUrl) {
|
||||
this.ssoUrl = ssoUrl;
|
||||
this.singleSignOn.setUrl(ssoUrl);
|
||||
}
|
||||
|
||||
public SingleSignOn getSingleSignOn() {
|
||||
return this.singleSignOn;
|
||||
}
|
||||
|
||||
public Verification getVerification() {
|
||||
return this.verification;
|
||||
}
|
||||
|
||||
public static class SingleSignOn {
|
||||
|
||||
/**
|
||||
* Remote endpoint to send authentication requests to.
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* Whether to redirect or post authentication requests.
|
||||
*/
|
||||
private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT;
|
||||
|
||||
/**
|
||||
* Whether to sign authentication requests.
|
||||
*/
|
||||
private boolean signRequest = true;
|
||||
|
||||
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 void setSignRequest(boolean signRequest) {
|
||||
this.signRequest = signRequest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Verification {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -66,15 +66,28 @@ class Saml2RelyingPartyRegistrationConfiguration {
|
|||
}
|
||||
|
||||
private RelyingPartyRegistration asRegistration(String id, Registration properties) {
|
||||
boolean signRequest = properties.getIdentityprovider().getSingleSignOn().isSignRequest();
|
||||
validateSigningCredentials(properties, signRequest);
|
||||
RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(id);
|
||||
builder.assertionConsumerServiceUrlTemplate(
|
||||
"{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
||||
builder.providerDetails((details) -> details.webSsoUrl(properties.getIdentityprovider().getSsoUrl()));
|
||||
builder.providerDetails(
|
||||
(details) -> details.webSsoUrl(properties.getIdentityprovider().getSingleSignOn().getUrl()));
|
||||
builder.providerDetails((details) -> details.entityId(properties.getIdentityprovider().getEntityId()));
|
||||
builder.providerDetails(
|
||||
(details) -> details.binding(properties.getIdentityprovider().getSingleSignOn().getBinding()));
|
||||
builder.providerDetails((details) -> details.signAuthNRequest(signRequest));
|
||||
builder.credentials((credentials) -> credentials.addAll(asCredentials(properties)));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
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 List<Saml2X509Credential> asCredentials(Registration properties) {
|
||||
List<Saml2X509Credential> credentials = new ArrayList<>();
|
||||
properties.getSigning().getCredentials().stream().map(this::asSigningCredential).forEach(credentials::add);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import org.springframework.security.config.BeanIds;
|
|||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
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.servlet.filter.Saml2WebSsoAuthenticationFilter;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
|
@ -85,11 +86,28 @@ public class Saml2RelyingPartyAutoConfigurationTests {
|
|||
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
|
||||
assertThat(registration.getAssertionConsumerServiceUrlTemplate())
|
||||
.isEqualTo("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
||||
assertThat(registration.getProviderDetails().getBinding()).isEqualTo(Saml2MessageBinding.POST);
|
||||
assertThat(registration.getProviderDetails().isSignAuthNRequest()).isEqualTo(false);
|
||||
assertThat(registration.getSigningCredentials()).isNotNull();
|
||||
assertThat(registration.getVerificationCredentials()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
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
|
||||
void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrowException() {
|
||||
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false))
|
||||
.run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() {
|
||||
this.contextRunner.withPropertyValues(getPropertyValues())
|
||||
|
|
@ -112,11 +130,22 @@ public class Saml2RelyingPartyAutoConfigurationTests {
|
|||
.run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse());
|
||||
}
|
||||
|
||||
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) {
|
||||
return new String[] { PREFIX
|
||||
+ ".foo.identityprovider.single-sign-on.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
|
||||
PREFIX + ".foo.identityprovider.single-sign-on.binding=post",
|
||||
PREFIX + ".foo.identityprovider.single-sign-on.sign-request=" + signRequests,
|
||||
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 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.single-sign-on.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
|
||||
PREFIX + ".foo.identityprovider.single-sign-on.binding=post",
|
||||
PREFIX + ".foo.identityprovider.single-sign-on.sign-request=false",
|
||||
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" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.security.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.Saml2MessageBinding;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Saml2RelyingPartyProperties}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class Saml2RelyingPartyPropertiesTests {
|
||||
|
||||
private final Saml2RelyingPartyProperties properties = new Saml2RelyingPartyProperties();
|
||||
|
||||
@Deprecated
|
||||
@Test
|
||||
void customizeSsoUrlDeprecated() {
|
||||
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.url",
|
||||
"https://simplesaml-for-spring-saml/SSOService.php");
|
||||
assertThat(
|
||||
this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn().getUrl())
|
||||
.isEqualTo("https://simplesaml-for-spring-saml/SSOService.php");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeSsoUrl() {
|
||||
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.url",
|
||||
"https://simplesaml-for-spring-saml/SSOService.php");
|
||||
assertThat(
|
||||
this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn().getUrl())
|
||||
.isEqualTo("https://simplesaml-for-spring-saml/SSOService.php");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeSsoBindingDefaultsToRedirect() {
|
||||
this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration());
|
||||
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
|
||||
.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeSsoBinding() {
|
||||
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.binding",
|
||||
"post");
|
||||
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
|
||||
.getBinding()).isEqualTo(Saml2MessageBinding.POST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeSsoSignRequests() {
|
||||
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.sign-request",
|
||||
"false");
|
||||
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
|
||||
.isSignRequest()).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeSsoSignRequestsIsTrueByDefault() {
|
||||
this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration());
|
||||
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
|
||||
.isSignRequest()).isEqualTo(true);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,7 +13,8 @@ spring:
|
|||
credentials:
|
||||
- certificate-location: "classpath:saml/certificate.txt"
|
||||
entity-id: simplesaml
|
||||
sso-url: https://simplesaml-for-spring-saml/SSOService.php
|
||||
single-sign-on:
|
||||
url: https://simplesaml-for-spring-saml/SSOService.php
|
||||
okta:
|
||||
signing:
|
||||
credentials:
|
||||
|
|
@ -24,4 +25,6 @@ spring:
|
|||
credentials:
|
||||
- certificate-location: "classpath:saml/certificate.txt"
|
||||
entity-id: okta-id-1234
|
||||
sso-url: https://okta-for-spring/saml2/idp/SSOService.php
|
||||
single-sign-on:
|
||||
url:
|
||||
https://okta-for-spring/saml2/idp/SSOService.php
|
||||
|
|
|
|||
Loading…
Reference in New Issue