Skip to content

Commit 7e38411

Browse files
committed
Add SecurityContextHolderStrategy XML Configuration for Saml2
Issue gh-11061
1 parent 19181a5 commit 7e38411

File tree

6 files changed

+140
-5
lines changed

6 files changed

+140
-5
lines changed

config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ final class AuthenticationConfigBuilder {
239239
createX509Filter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
240240
createJeeFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
241241
createLogoutFilter(authenticationFilterSecurityContextHolderStrategyRef);
242-
createSaml2LogoutFilter();
242+
createSaml2LogoutFilter(authenticationFilterSecurityContextHolderStrategyRef);
243243
createLoginPageFilterIfNeeded();
244244
createUserDetailsServiceFactory();
245245
createExceptionTranslationFilter(authenticationFilterSecurityContextHolderStrategyRef);
@@ -635,13 +635,13 @@ void createLogoutFilter(BeanMetadataElement authenticationFilterSecurityContextH
635635
}
636636
}
637637

638-
private void createSaml2LogoutFilter() {
638+
private void createSaml2LogoutFilter(BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
639639
Element saml2LogoutElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.SAML2_LOGOUT);
640640
if (saml2LogoutElt == null) {
641641
return;
642642
}
643643
Saml2LogoutBeanDefinitionParser parser = new Saml2LogoutBeanDefinitionParser(this.logoutHandlers,
644-
this.logoutSuccessHandler);
644+
this.logoutSuccessHandler, authenticationFilterSecurityContextHolderStrategyRef);
645645
parser.parse(saml2LogoutElt, this.pc);
646646
BeanDefinition saml2LogoutFilter = parser.getLogoutFilter();
647647
BeanDefinition saml2LogoutRequestFilter = parser.getLogoutRequestFilter();

config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,14 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser {
7777

7878
private BeanDefinition logoutFilter;
7979

80+
private BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
81+
8082
Saml2LogoutBeanDefinitionParser(ManagedList<BeanMetadataElement> logoutHandlers,
81-
BeanMetadataElement logoutSuccessHandler) {
83+
BeanMetadataElement logoutSuccessHandler,
84+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
8285
this.logoutHandlers = logoutHandlers;
8386
this.logoutSuccessHandler = logoutSuccessHandler;
87+
this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
8488
}
8589

8690
@Override
@@ -119,7 +123,10 @@ public BeanDefinition parse(Element element, ParserContext pc) {
119123
this.logoutRequestFilter = BeanDefinitionBuilder.rootBeanDefinition(Saml2LogoutRequestFilter.class)
120124
.addConstructorArgValue(registrations).addConstructorArgValue(logoutRequestValidator)
121125
.addConstructorArgValue(logoutResponseResolver).addConstructorArgValue(this.logoutHandlers)
122-
.addPropertyValue("logoutRequestMatcher", logoutRequestMatcher).getBeanDefinition();
126+
.addPropertyValue("logoutRequestMatcher", logoutRequestMatcher)
127+
.addPropertyValue("securityContextHolderStrategy",
128+
this.authenticationFilterSecurityContextHolderStrategy)
129+
.getBeanDefinition();
123130
BeanMetadataElement logoutResponseValidator = Saml2LogoutBeanDefinitionParserUtils
124131
.getLogoutResponseValidator(element);
125132
BeanMetadataElement logoutRequestRepository = Saml2LogoutBeanDefinitionParserUtils

config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.security.config.test.SpringTestContextExtension;
3333
import org.springframework.security.core.Authentication;
3434
import org.springframework.security.core.AuthenticationException;
35+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3536
import org.springframework.security.saml2.core.Saml2ParameterNames;
3637
import org.springframework.security.saml2.core.Saml2Utils;
3738
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -61,6 +62,7 @@
6162
import static org.mockito.ArgumentMatchers.any;
6263
import static org.mockito.ArgumentMatchers.anyString;
6364
import static org.mockito.BDDMockito.given;
65+
import static org.mockito.Mockito.atLeastOnce;
6466
import static org.mockito.Mockito.mock;
6567
import static org.mockito.Mockito.verify;
6668
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -178,6 +180,23 @@ public void authenticateWhenAuthenticationResponseValidThenAuthenticate() throws
178180
assertThat(authentication.getPrincipal()).isInstanceOf(Saml2AuthenticatedPrincipal.class);
179181
}
180182

183+
@Test
184+
public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
185+
this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire();
186+
RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential();
187+
// @formatter:off
188+
this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE))
189+
.andDo(MockMvcResultHandlers.print())
190+
.andExpect(status().is2xxSuccessful());
191+
// @formatter:on
192+
ArgumentCaptor<Authentication> authenticationCaptor = ArgumentCaptor.forClass(Authentication.class);
193+
verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture());
194+
Authentication authentication = authenticationCaptor.getValue();
195+
assertThat(authentication.getPrincipal()).isInstanceOf(Saml2AuthenticatedPrincipal.class);
196+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
197+
verify(strategy, atLeastOnce()).getContext();
198+
}
199+
181200
@Test
182201
public void authenticateWhenAuthenticationResponseValidThenAuthenticationSuccessEventPublished() throws Exception {
183202
this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository")).autowire();

config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.security.config.test.SpringTestContext;
3636
import org.springframework.security.config.test.SpringTestContextExtension;
3737
import org.springframework.security.core.authority.AuthorityUtils;
38+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3839
import org.springframework.security.saml2.core.Saml2Utils;
3940
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
4041
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
@@ -63,6 +64,7 @@
6364
import static org.hamcrest.Matchers.containsString;
6465
import static org.mockito.ArgumentMatchers.any;
6566
import static org.mockito.BDDMockito.given;
67+
import static org.mockito.Mockito.atLeastOnce;
6668
import static org.mockito.Mockito.verify;
6769
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
6870
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -233,6 +235,23 @@ public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() th
233235
assertThat(location).startsWith("https://ap.example.org/logout/saml2/response");
234236
}
235237

238+
@Test
239+
public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
240+
this.spring.configLocations(this.xml("WithSecurityContextHolderStrategy")).autowire();
241+
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
242+
Collections.emptyMap());
243+
principal.setRelyingPartyRegistrationId("get");
244+
Saml2Authentication user = new Saml2Authentication(principal, "response",
245+
AuthorityUtils.createAuthorityList("ROLE_USER"));
246+
MvcResult result = this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
247+
.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
248+
.param("Signature", this.apLogoutRequestSignature).with(samlQueryString()).with(authentication(user)))
249+
.andExpect(status().isFound()).andReturn();
250+
String location = result.getResponse().getHeader("Location");
251+
assertThat(location).startsWith("https://ap.example.org/logout/saml2/response");
252+
verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext();
253+
}
254+
236255
@Test
237256
public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception {
238257
this.spring.configLocations(this.xml("Default")).autowire();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2021 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ https://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xmlns="http://www.springframework.org/schema/security"
21+
xsi:schemaLocation="
22+
http://www.springframework.org/schema/security
23+
https://www.springframework.org/schema/security/spring-security.xsd
24+
http://www.springframework.org/schema/beans
25+
https://www.springframework.org/schema/beans/spring-beans.xsd">
26+
27+
<http auto-config="true" security-context-holder-strategy-ref="ref">
28+
<intercept-url pattern="/**" access="authenticated"/>
29+
<saml2-login authentication-success-handler-ref="authenticationSuccessHandler" relying-party-registration-repository-ref="relyingPartyRegistrationRepository"/>
30+
</http>
31+
32+
<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
33+
<b:constructor-arg>
34+
<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
35+
</b:constructor-arg>
36+
</b:bean>
37+
38+
<b:bean id="authenticationSuccessListener" class="org.mockito.Mockito" factory-method="mock">
39+
<b:constructor-arg value="org.springframework.context.ApplicationListener"/>
40+
</b:bean>
41+
<b:bean id="authenticationSuccessHandler" class="org.mockito.Mockito" factory-method="mock">
42+
<b:constructor-arg value="org.springframework.security.web.authentication.AuthenticationSuccessHandler"/>
43+
</b:bean>
44+
<b:bean id="relyingPartyRegistrationRepository" class="org.mockito.Mockito" factory-method="mock">
45+
<b:constructor-arg value="org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"/>
46+
</b:bean>
47+
48+
<b:import resource="userservice.xml"/>
49+
</b:beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2022 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ https://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xmlns="http://www.springframework.org/schema/security"
21+
xsi:schemaLocation="
22+
http://www.springframework.org/schema/security
23+
https://www.springframework.org/schema/security/spring-security.xsd
24+
http://www.springframework.org/schema/beans
25+
https://www.springframework.org/schema/beans/spring-beans.xsd">
26+
27+
<http auto-config="true" security-context-holder-strategy-ref="ref">
28+
<intercept-url pattern="/**" access="authenticated"/>
29+
<saml2-login/>
30+
<saml2-logout/>
31+
</http>
32+
33+
<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
34+
<b:constructor-arg>
35+
<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
36+
</b:constructor-arg>
37+
</b:bean>
38+
39+
<b:import resource="userservice.xml"/>
40+
<b:import resource="../saml2/logout-registrations.xml"/>
41+
</b:beans>

0 commit comments

Comments
 (0)