Skip to content

Commit 8659102

Browse files
committed
Add configuration options for SAML authentication requests
Closes gh-20584
1 parent 6a0d620 commit 8659102

File tree

5 files changed

+203
-7
lines changed

5 files changed

+203
-7
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323

2424
import org.springframework.boot.context.properties.ConfigurationProperties;
2525
import org.springframework.core.io.Resource;
26+
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
2627

2728
/**
2829
* SAML2 relying party properties.
@@ -124,6 +125,8 @@ public static class Identityprovider {
124125
*/
125126
private String ssoUrl;
126127

128+
private SingleSignOn singleSignOn = new SingleSignOn();
129+
127130
private Verification verification = new Verification();
128131

129132
public String getEntityId() {
@@ -134,18 +137,67 @@ public void setEntityId(String entityId) {
134137
this.entityId = entityId;
135138
}
136139

140+
@Deprecated
137141
public String getSsoUrl() {
138-
return this.ssoUrl;
142+
return this.getSingleSignOn().getUrl();
139143
}
140144

145+
@Deprecated
141146
public void setSsoUrl(String ssoUrl) {
142-
this.ssoUrl = ssoUrl;
147+
this.singleSignOn.setUrl(ssoUrl);
148+
}
149+
150+
public SingleSignOn getSingleSignOn() {
151+
return this.singleSignOn;
143152
}
144153

145154
public Verification getVerification() {
146155
return this.verification;
147156
}
148157

158+
public static class SingleSignOn {
159+
160+
/**
161+
* Remote endpoint to send authentication requests to.
162+
*/
163+
private String url;
164+
165+
/**
166+
* Whether to redirect or post authentication requests.
167+
*/
168+
private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT;
169+
170+
/**
171+
* Whether to sign authentication requests.
172+
*/
173+
private boolean signRequest = true;
174+
175+
public String getUrl() {
176+
return this.url;
177+
}
178+
179+
public void setUrl(String url) {
180+
this.url = url;
181+
}
182+
183+
public Saml2MessageBinding getBinding() {
184+
return this.binding;
185+
}
186+
187+
public void setBinding(Saml2MessageBinding binding) {
188+
this.binding = binding;
189+
}
190+
191+
public boolean isSignRequest() {
192+
return this.signRequest;
193+
}
194+
195+
public void setSignRequest(boolean signRequest) {
196+
this.signRequest = signRequest;
197+
}
198+
199+
}
200+
149201
public static class Verification {
150202

151203
/**

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,28 @@ private RelyingPartyRegistration asRegistration(Map.Entry<String, Registration>
6666
}
6767

6868
private RelyingPartyRegistration asRegistration(String id, Registration properties) {
69+
boolean signRequest = properties.getIdentityprovider().getSingleSignOn().isSignRequest();
70+
validateSigningCredentials(properties, signRequest);
6971
RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(id);
7072
builder.assertionConsumerServiceUrlTemplate(
7173
"{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
72-
builder.providerDetails((details) -> details.webSsoUrl(properties.getIdentityprovider().getSsoUrl()));
74+
builder.providerDetails(
75+
(details) -> details.webSsoUrl(properties.getIdentityprovider().getSingleSignOn().getUrl()));
7376
builder.providerDetails((details) -> details.entityId(properties.getIdentityprovider().getEntityId()));
77+
builder.providerDetails(
78+
(details) -> details.binding(properties.getIdentityprovider().getSingleSignOn().getBinding()));
79+
builder.providerDetails((details) -> details.signAuthNRequest(signRequest));
7480
builder.credentials((credentials) -> credentials.addAll(asCredentials(properties)));
7581
return builder.build();
7682
}
7783

84+
private void validateSigningCredentials(Registration properties, boolean signRequest) {
85+
if (signRequest) {
86+
Assert.state(!properties.getSigning().getCredentials().isEmpty(),
87+
"Signing credentials must not be empty when authentication requests require signing.");
88+
}
89+
}
90+
7891
private List<Saml2X509Credential> asCredentials(Registration properties) {
7992
List<Saml2X509Credential> credentials = new ArrayList<>();
8093
properties.getSigning().getCredentials().stream().map(this::asSigningCredential).forEach(credentials::add);

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyAutoConfigurationTests.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
3535
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
3636
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
37+
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
3738
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
3839
import org.springframework.security.web.FilterChainProxy;
3940
import org.springframework.security.web.SecurityFilterChain;
@@ -85,11 +86,28 @@ void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent(
8586
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
8687
assertThat(registration.getAssertionConsumerServiceUrlTemplate())
8788
.isEqualTo("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
89+
assertThat(registration.getProviderDetails().getBinding()).isEqualTo(Saml2MessageBinding.POST);
90+
assertThat(registration.getProviderDetails().isSignAuthNRequest()).isEqualTo(false);
8891
assertThat(registration.getSigningCredentials()).isNotNull();
8992
assertThat(registration.getVerificationCredentials()).isNotNull();
9093
});
9194
}
9295

96+
@Test
97+
void autoConfigurationWhenSignRequestsTrueAndNoSigningCredentialsShouldThrowException() {
98+
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(true)).run((context) -> {
99+
assertThat(context).hasFailed();
100+
assertThat(context.getStartupFailure()).hasMessageContaining(
101+
"Signing credentials must not be empty when authentication requests require signing.");
102+
});
103+
}
104+
105+
@Test
106+
void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrowException() {
107+
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false))
108+
.run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class));
109+
}
110+
93111
@Test
94112
void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() {
95113
this.contextRunner.withPropertyValues(getPropertyValues())
@@ -112,11 +130,22 @@ void samlLoginShouldBackOffWhenAWebSecurityConfigurerAdapterIsDefined() {
112130
.run((context) -> assertThat(hasFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse());
113131
}
114132

133+
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) {
134+
return new String[] { PREFIX
135+
+ ".foo.identityprovider.single-sign-on.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
136+
PREFIX + ".foo.identityprovider.single-sign-on.binding=post",
137+
PREFIX + ".foo.identityprovider.single-sign-on.sign-request=" + signRequests,
138+
PREFIX + ".foo.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
139+
PREFIX + ".foo.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location" };
140+
}
141+
115142
private String[] getPropertyValues() {
116143
return new String[] {
117144
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:saml/private-key-location",
118145
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:saml/certificate-location",
119-
PREFIX + ".foo.identityprovider.sso-url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
146+
PREFIX + ".foo.identityprovider.single-sign-on.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
147+
PREFIX + ".foo.identityprovider.single-sign-on.binding=post",
148+
PREFIX + ".foo.identityprovider.single-sign-on.sign-request=false",
120149
PREFIX + ".foo.identityprovider.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
121150
PREFIX + ".foo.identityprovider.verification.credentials[0].certificate-location=classpath:saml/certificate-location" };
122151
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security.saml2;
18+
19+
import java.util.Collections;
20+
import java.util.Map;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.boot.context.properties.bind.Bindable;
25+
import org.springframework.boot.context.properties.bind.Binder;
26+
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
27+
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
28+
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* Tests for {@link Saml2RelyingPartyProperties}.
34+
*
35+
* @author Madhura Bhave
36+
*/
37+
class Saml2RelyingPartyPropertiesTests {
38+
39+
private final Saml2RelyingPartyProperties properties = new Saml2RelyingPartyProperties();
40+
41+
@Deprecated
42+
@Test
43+
void customizeSsoUrlDeprecated() {
44+
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.url",
45+
"https://simplesaml-for-spring-saml/SSOService.php");
46+
assertThat(
47+
this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn().getUrl())
48+
.isEqualTo("https://simplesaml-for-spring-saml/SSOService.php");
49+
}
50+
51+
@Test
52+
void customizeSsoUrl() {
53+
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.url",
54+
"https://simplesaml-for-spring-saml/SSOService.php");
55+
assertThat(
56+
this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn().getUrl())
57+
.isEqualTo("https://simplesaml-for-spring-saml/SSOService.php");
58+
}
59+
60+
@Test
61+
void customizeSsoBindingDefaultsToRedirect() {
62+
this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration());
63+
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
64+
.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
65+
}
66+
67+
@Test
68+
void customizeSsoBinding() {
69+
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.binding",
70+
"post");
71+
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
72+
.getBinding()).isEqualTo(Saml2MessageBinding.POST);
73+
}
74+
75+
@Test
76+
void customizeSsoSignRequests() {
77+
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.identity-provider.single-sign-on.sign-request",
78+
"false");
79+
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
80+
.isSignRequest()).isEqualTo(false);
81+
}
82+
83+
@Test
84+
void customizeSsoSignRequestsIsTrueByDefault() {
85+
this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration());
86+
assertThat(this.properties.getRegistration().get("simplesamlphp").getIdentityprovider().getSingleSignOn()
87+
.isSignRequest()).isEqualTo(true);
88+
}
89+
90+
private void bind(String name, String value) {
91+
bind(Collections.singletonMap(name, value));
92+
}
93+
94+
private void bind(Map<String, String> map) {
95+
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
96+
new Binder(source).bind("spring.security.saml2.relyingparty", Bindable.ofInstance(this.properties));
97+
}
98+
99+
}

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-saml2-service-provider/src/main/resources/application.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ spring:
1313
credentials:
1414
- certificate-location: "classpath:saml/certificate.txt"
1515
entity-id: simplesaml
16-
sso-url: https://simplesaml-for-spring-saml/SSOService.php
16+
single-sign-on:
17+
url: https://simplesaml-for-spring-saml/SSOService.php
1718
okta:
1819
signing:
1920
credentials:
@@ -24,4 +25,6 @@ spring:
2425
credentials:
2526
- certificate-location: "classpath:saml/certificate.txt"
2627
entity-id: okta-id-1234
27-
sso-url: https://okta-for-spring/saml2/idp/SSOService.php
28+
single-sign-on:
29+
url:
30+
https://okta-for-spring/saml2/idp/SSOService.php

0 commit comments

Comments
 (0)