Skip to content

Commit 83b3bb3

Browse files
committed
Add SecurityContextHolderStrategy to Pre-authenticated scenarios
Issue gh-11060 Issue gh-11061
1 parent 97cb2a7 commit 83b3bb3

File tree

8 files changed

+179
-17
lines changed

8 files changed

+179
-17
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -198,7 +198,8 @@ public void init(H http) {
198198

199199
@Override
200200
public void configure(H http) {
201-
J2eePreAuthenticatedProcessingFilter filter = getFilter(http.getSharedObject(AuthenticationManager.class));
201+
J2eePreAuthenticatedProcessingFilter filter = getFilter(http.getSharedObject(AuthenticationManager.class),
202+
http);
202203
http.addFilter(filter);
203204
}
204205

@@ -208,12 +209,14 @@ public void configure(H http) {
208209
* @param authenticationManager the {@link AuthenticationManager} to use.
209210
* @return the {@link J2eePreAuthenticatedProcessingFilter} to use.
210211
*/
211-
private J2eePreAuthenticatedProcessingFilter getFilter(AuthenticationManager authenticationManager) {
212+
private J2eePreAuthenticatedProcessingFilter getFilter(AuthenticationManager authenticationManager, H http) {
212213
if (this.j2eePreAuthenticatedProcessingFilter == null) {
213214
this.j2eePreAuthenticatedProcessingFilter = new J2eePreAuthenticatedProcessingFilter();
214215
this.j2eePreAuthenticatedProcessingFilter.setAuthenticationManager(authenticationManager);
215216
this.j2eePreAuthenticatedProcessingFilter
216217
.setAuthenticationDetailsSource(createWebAuthenticationDetailsSource());
218+
this.j2eePreAuthenticatedProcessingFilter
219+
.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
217220
this.j2eePreAuthenticatedProcessingFilter = postProcess(this.j2eePreAuthenticatedProcessingFilter);
218221
}
219222

config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,11 @@ public void init(H http) {
183183

184184
@Override
185185
public void configure(H http) {
186-
X509AuthenticationFilter filter = getFilter(http.getSharedObject(AuthenticationManager.class));
186+
X509AuthenticationFilter filter = getFilter(http.getSharedObject(AuthenticationManager.class), http);
187187
http.addFilter(filter);
188188
}
189189

190-
private X509AuthenticationFilter getFilter(AuthenticationManager authenticationManager) {
190+
private X509AuthenticationFilter getFilter(AuthenticationManager authenticationManager, H http) {
191191
if (this.x509AuthenticationFilter == null) {
192192
this.x509AuthenticationFilter = new X509AuthenticationFilter();
193193
this.x509AuthenticationFilter.setAuthenticationManager(authenticationManager);
@@ -197,6 +197,7 @@ private X509AuthenticationFilter getFilter(AuthenticationManager authenticationM
197197
if (this.authenticationDetailsSource != null) {
198198
this.x509AuthenticationFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
199199
}
200+
this.x509AuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
200201
this.x509AuthenticationFilter = postProcess(this.x509AuthenticationFilter);
201202
}
202203

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ final class AuthenticationConfigBuilder {
236236
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
237237
authenticationFilterSecurityContextRepositoryRef);
238238
createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
239-
createX509Filter(authenticationManager);
240-
createJeeFilter(authenticationManager);
239+
createX509Filter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
240+
createJeeFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
241241
createLogoutFilter(authenticationFilterSecurityContextHolderStrategyRef);
242242
createSaml2LogoutFilter();
243243
createLoginPageFilterIfNeeded();
@@ -488,14 +488,17 @@ void createBearerTokenAuthenticationFilter(BeanReference authManager) {
488488
this.bearerTokenAuthenticationFilter = resourceServerBuilder.parse(resourceServerElt, this.pc);
489489
}
490490

491-
void createX509Filter(BeanReference authManager) {
491+
void createX509Filter(BeanReference authManager,
492+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
492493
Element x509Elt = DomUtils.getChildElementByTagName(this.httpElt, Elements.X509);
493494
RootBeanDefinition filter = null;
494495
if (x509Elt != null) {
495496
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
496497
.rootBeanDefinition(X509AuthenticationFilter.class);
497498
filterBuilder.getRawBeanDefinition().setSource(this.pc.extractSource(x509Elt));
498499
filterBuilder.addPropertyValue("authenticationManager", authManager);
500+
filterBuilder.addPropertyValue("securityContextHolderStrategy",
501+
authenticationFilterSecurityContextHolderStrategyRef);
499502
String regex = x509Elt.getAttribute("subject-principal-regex");
500503
if (StringUtils.hasText(regex)) {
501504
BeanDefinitionBuilder extractor = BeanDefinitionBuilder
@@ -536,14 +539,17 @@ private void createPrauthEntryPoint(Element source) {
536539
}
537540
}
538541

539-
void createJeeFilter(BeanReference authManager) {
542+
void createJeeFilter(BeanReference authManager,
543+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
540544
Element jeeElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.JEE);
541545
RootBeanDefinition filter = null;
542546
if (jeeElt != null) {
543547
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
544548
.rootBeanDefinition(J2eePreAuthenticatedProcessingFilter.class);
545549
filterBuilder.getRawBeanDefinition().setSource(this.pc.extractSource(jeeElt));
546550
filterBuilder.addPropertyValue("authenticationManager", authManager);
551+
filterBuilder.addPropertyValue("securityContextHolderStrategy",
552+
authenticationFilterSecurityContextHolderStrategyRef);
547553
BeanDefinitionBuilder adsBldr = BeanDefinitionBuilder
548554
.rootBeanDefinition(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class);
549555
adsBldr.addPropertyValue("userRoles2GrantedAuthoritiesMapper",

config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,30 @@
2828
import org.springframework.context.annotation.Bean;
2929
import org.springframework.core.io.ClassPathResource;
3030
import org.springframework.security.config.annotation.ObjectPostProcessor;
31+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
3132
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3233
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3334
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3435
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
3536
import org.springframework.security.config.test.SpringTestContext;
3637
import org.springframework.security.config.test.SpringTestContextExtension;
38+
import org.springframework.security.core.context.SecurityContextChangedListener;
39+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3740
import org.springframework.security.core.userdetails.User;
3841
import org.springframework.security.core.userdetails.UserDetailsService;
3942
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4043
import org.springframework.security.web.SecurityFilterChain;
44+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
4145
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
4246
import org.springframework.test.web.servlet.MockMvc;
4347

4448
import static org.mockito.ArgumentMatchers.any;
49+
import static org.mockito.Mockito.atLeastOnce;
4550
import static org.mockito.Mockito.mock;
4651
import static org.mockito.Mockito.spy;
4752
import static org.mockito.Mockito.verify;
4853
import static org.springframework.security.config.Customizer.withDefaults;
54+
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
4955
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
5056
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
5157
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -90,6 +96,21 @@ public void x509WhenConfiguredInLambdaThenUsesDefaults() throws Exception {
9096
// @formatter:on
9197
}
9298

99+
@Test
100+
public void x509WhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
101+
this.spring.register(DefaultsInLambdaConfig.class, SecurityContextChangedListenerConfig.class).autowire();
102+
X509Certificate certificate = loadCert("rod.cer");
103+
// @formatter:off
104+
this.mvc.perform(get("/").with(x509(certificate)))
105+
.andExpect(authenticated().withUsername("rod"));
106+
// @formatter:on
107+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
108+
verify(strategy, atLeastOnce()).getContext();
109+
SecurityContextChangedListener listener = this.spring.getContext()
110+
.getBean(SecurityContextChangedListener.class);
111+
verify(listener).securityContextChanged(setAuthentication(PreAuthenticatedAuthenticationToken.class));
112+
}
113+
93114
@Test
94115
public void x509WhenSubjectPrincipalRegexInLambdaThenUsesRegexToExtractPrincipal() throws Exception {
95116
this.spring.register(SubjectPrincipalRegexInLambdaConfig.class).autowire();

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.springframework.security.core.authority.AuthorityUtils;
7777
import org.springframework.security.core.context.SecurityContext;
7878
import org.springframework.security.core.context.SecurityContextHolder;
79+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
7980
import org.springframework.security.core.context.SecurityContextImpl;
8081
import org.springframework.security.web.AuthenticationEntryPoint;
8182
import org.springframework.security.web.FilterChainProxy;
@@ -380,6 +381,19 @@ public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsCon
380381
// @formatter:on
381382
}
382383

384+
@Test
385+
public void getWhenUsingX509CustomSecurityContextHolderStrategyThenUses() throws Exception {
386+
System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)");
387+
this.spring.configLocations(xml("X509WithSecurityContextHolderStrategy")).autowire();
388+
RequestPostProcessor x509 = x509(
389+
"classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem");
390+
// @formatter:off
391+
this.mvc.perform(get("/protected").with(x509))
392+
.andExpect(status().isOk());
393+
// @formatter:on
394+
verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext();
395+
}
396+
383397
@Test
384398
public void configureWhenUsingInvalidLogoutSuccessUrlThenThrowsException() {
385399
assertThatExceptionOfType(BeanCreationException.class)
@@ -652,6 +666,26 @@ public void loginWhenJeeFilterThenExtractsRoles() throws Exception {
652666
// @formatter:on
653667
}
654668

669+
@Test
670+
public void loginWhenJeeFilterCustomSecurityContextHolderStrategyThenUses() throws Exception {
671+
this.spring.configLocations(xml("JeeFilterWithSecurityContextHolderStrategy")).autowire();
672+
Principal user = mock(Principal.class);
673+
given(user.getName()).willReturn("joe");
674+
// @formatter:off
675+
MockHttpServletRequestBuilder rolesRequest = get("/roles")
676+
.principal(user)
677+
.with((request) -> {
678+
request.addUserRole("admin");
679+
request.addUserRole("user");
680+
request.addUserRole("unmapped");
681+
return request;
682+
});
683+
this.mvc.perform(rolesRequest)
684+
.andExpect(content().string("ROLE_admin,ROLE_user"));
685+
// @formatter:on
686+
verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext();
687+
}
688+
655689
@Test
656690
public void loginWhenUsingCustomAuthenticationDetailsSourceRefThenAuthenticationSourcesDetailsAccordingly()
657691
throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2018 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 security-context-holder-strategy-ref="ref">
28+
<jee mappable-roles="admin,user"/>
29+
</http>
30+
31+
<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
32+
<b:constructor-arg>
33+
<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
34+
</b:constructor-arg>
35+
</b:bean>
36+
37+
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
38+
<b:import resource="userservice.xml"/>
39+
</b:beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2018 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 security-context-holder-strategy-ref="ref">
28+
<x509 subject-principal-regex="${subject_principal_regex:(.*)}"/>
29+
<intercept-url pattern="/**" access="authenticated"/>
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 name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
39+
40+
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
41+
<b:import resource="userservice.xml"/>
42+
</b:beans>

web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -36,6 +36,7 @@
3636
import org.springframework.security.core.AuthenticationException;
3737
import org.springframework.security.core.context.SecurityContext;
3838
import org.springframework.security.core.context.SecurityContextHolder;
39+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3940
import org.springframework.security.web.WebAttributes;
4041
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
4142
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -88,6 +89,9 @@
8889
public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFilterBean
8990
implements ApplicationEventPublisherAware {
9091

92+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
93+
.getContextHolderStrategy();
94+
9195
private ApplicationEventPublisher eventPublisher = null;
9296

9397
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
@@ -132,8 +136,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
132136
throws IOException, ServletException {
133137
if (this.requiresAuthenticationRequestMatcher.matches((HttpServletRequest) request)) {
134138
if (logger.isDebugEnabled()) {
135-
logger.debug(LogMessage
136-
.of(() -> "Authenticating " + SecurityContextHolder.getContext().getAuthentication()));
139+
logger.debug(LogMessage.of(
140+
() -> "Authenticating " + this.securityContextHolderStrategy.getContext().getAuthentication()));
137141
}
138142
doAuthenticate((HttpServletRequest) request, (HttpServletResponse) response);
139143
}
@@ -211,9 +215,9 @@ private void doAuthenticate(HttpServletRequest request, HttpServletResponse resp
211215
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
212216
Authentication authResult) throws IOException, ServletException {
213217
this.logger.debug(LogMessage.format("Authentication success: %s", authResult));
214-
SecurityContext context = SecurityContextHolder.createEmptyContext();
218+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
215219
context.setAuthentication(authResult);
216-
SecurityContextHolder.setContext(context);
220+
this.securityContextHolderStrategy.setContext(context);
217221
this.securityContextRepository.saveContext(context, request, response);
218222
if (this.eventPublisher != null) {
219223
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
@@ -231,7 +235,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
231235
*/
232236
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
233237
AuthenticationException failed) throws IOException, ServletException {
234-
SecurityContextHolder.clearContext();
238+
this.securityContextHolderStrategy.clearContext();
235239
this.logger.debug("Cleared security context due to exception", failed);
236240
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, failed);
237241
if (this.authenticationFailureHandler != null) {
@@ -335,6 +339,17 @@ public void setRequiresAuthenticationRequestMatcher(RequestMatcher requiresAuthe
335339
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
336340
}
337341

342+
/**
343+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
344+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
345+
*
346+
* @since 5.8
347+
*/
348+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
349+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
350+
this.securityContextHolderStrategy = securityContextHolderStrategy;
351+
}
352+
338353
/**
339354
* Override to extract the principal information from the current request
340355
*/
@@ -354,7 +369,8 @@ private class PreAuthenticatedProcessingRequestMatcher implements RequestMatcher
354369

355370
@Override
356371
public boolean matches(HttpServletRequest request) {
357-
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
372+
Authentication currentUser = AbstractPreAuthenticatedProcessingFilter.this.securityContextHolderStrategy
373+
.getContext().getAuthentication();
358374
if (currentUser == null) {
359375
return true;
360376
}
@@ -367,7 +383,7 @@ public boolean matches(HttpServletRequest request) {
367383
AbstractPreAuthenticatedProcessingFilter.this.logger
368384
.debug("Pre-authenticated principal has changed and will be reauthenticated");
369385
if (AbstractPreAuthenticatedProcessingFilter.this.invalidateSessionOnPrincipalChange) {
370-
SecurityContextHolder.clearContext();
386+
AbstractPreAuthenticatedProcessingFilter.this.securityContextHolderStrategy.clearContext();
371387
HttpSession session = request.getSession(false);
372388
if (session != null) {
373389
AbstractPreAuthenticatedProcessingFilter.this.logger.debug("Invalidating existing session");

0 commit comments

Comments
 (0)