Skip to content

Commit 2a70707

Browse files
committed
Add SecurityContextHolderStrategy XML Configuration for Defaults
Issue gh-11061
1 parent 2c09a30 commit 2a70707

File tree

10 files changed

+162
-11
lines changed

10 files changed

+162
-11
lines changed

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

+23-10
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ final class AuthenticationConfigBuilder {
236236

237237
AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc,
238238
SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager,
239+
BeanReference authenticationFilterSecurityContextHolderStrategyRef,
239240
BeanReference authenticationFilterSecurityContextRepositoryRef, BeanReference sessionStrategy,
240241
BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
241242
this.httpElt = element;
@@ -247,23 +248,24 @@ final class AuthenticationConfigBuilder {
247248
this.portMapper = portMapper;
248249
this.portResolver = portResolver;
249250
this.csrfLogoutHandler = csrfLogoutHandler;
250-
createAnonymousFilter();
251+
createAnonymousFilter(authenticationFilterSecurityContextHolderStrategyRef);
251252
createRememberMeFilter(authenticationManager);
252-
createBasicFilter(authenticationManager);
253+
createBasicFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
253254
createBearerTokenAuthenticationFilter(authenticationManager);
254-
createFormLoginFilter(sessionStrategy, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
255+
createFormLoginFilter(sessionStrategy, authenticationManager,
256+
authenticationFilterSecurityContextHolderStrategyRef, authenticationFilterSecurityContextRepositoryRef);
255257
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
256258
authenticationFilterSecurityContextRepositoryRef);
257259
createOpenIDLoginFilter(sessionStrategy, authenticationManager,
258260
authenticationFilterSecurityContextRepositoryRef);
259261
createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
260262
createX509Filter(authenticationManager);
261263
createJeeFilter(authenticationManager);
262-
createLogoutFilter();
264+
createLogoutFilter(authenticationFilterSecurityContextHolderStrategyRef);
263265
createSaml2LogoutFilter();
264266
createLoginPageFilterIfNeeded();
265267
createUserDetailsServiceFactory();
266-
createExceptionTranslationFilter();
268+
createExceptionTranslationFilter(authenticationFilterSecurityContextHolderStrategyRef);
267269
}
268270

269271
void createRememberMeFilter(BeanReference authenticationManager) {
@@ -293,6 +295,7 @@ private void createRememberMeProvider(String key) {
293295
}
294296

295297
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager,
298+
BeanReference authenticationFilterSecurityContextHolderStrategyRef,
296299
BeanReference authenticationFilterSecurityContextRepositoryRef) {
297300
Element formLoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.FORM_LOGIN);
298301
RootBeanDefinition formFilter = null;
@@ -313,6 +316,8 @@ void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authMana
313316
formFilter.getPropertyValues().addPropertyValue("securityContextRepository",
314317
authenticationFilterSecurityContextRepositoryRef);
315318
}
319+
formFilter.getPropertyValues().addPropertyValue("securityContextHolderStrategy",
320+
authenticationFilterSecurityContextHolderStrategyRef);
316321
// Id is required by login page filter
317322
this.formFilterId = this.pc.getReaderContext().generateBeanName(formFilter);
318323
this.pc.registerBeanComponent(new BeanComponentDefinition(formFilter, this.formFilterId));
@@ -564,7 +569,8 @@ private void injectRememberMeServicesRef(RootBeanDefinition bean, String remembe
564569
}
565570
}
566571

567-
void createBasicFilter(BeanReference authManager) {
572+
void createBasicFilter(BeanReference authManager,
573+
BeanReference authenticationFilterSecurityContextHolderStrategyRef) {
568574
Element basicAuthElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.BASIC_AUTH);
569575
if (basicAuthElt == null && !this.autoConfig) {
570576
// No basic auth, do nothing
@@ -592,6 +598,8 @@ void createBasicFilter(BeanReference authManager) {
592598
}
593599
filterBuilder.addConstructorArgValue(authManager);
594600
filterBuilder.addConstructorArgValue(this.basicEntryPoint);
601+
filterBuilder.addPropertyValue("securityContextHolderStrategy",
602+
authenticationFilterSecurityContextHolderStrategyRef);
595603
this.basicFilter = filterBuilder.getBeanDefinition();
596604
}
597605

@@ -739,15 +747,16 @@ void createLoginPageFilterIfNeeded() {
739747
}
740748
}
741749

742-
void createLogoutFilter() {
750+
void createLogoutFilter(BeanReference authenticationFilterSecurityContextHolderStrategyRef) {
743751
Element logoutElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.LOGOUT);
744752
if (logoutElt != null || this.autoConfig) {
745753
String formLoginPage = this.formLoginPage;
746754
if (formLoginPage == null) {
747755
formLoginPage = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL;
748756
}
749757
LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(formLoginPage,
750-
this.rememberMeServicesId, this.csrfLogoutHandler);
758+
this.rememberMeServicesId, this.csrfLogoutHandler,
759+
authenticationFilterSecurityContextHolderStrategyRef);
751760
this.logoutFilter = logoutParser.parse(logoutElt, this.pc);
752761
this.logoutHandlers = logoutParser.getLogoutHandlers();
753762
this.logoutSuccessHandler = logoutParser.getLogoutSuccessHandler();
@@ -803,7 +812,7 @@ List<BeanDefinition> getCsrfIgnoreRequestMatchers() {
803812
return this.csrfIgnoreRequestMatchers;
804813
}
805814

806-
void createAnonymousFilter() {
815+
void createAnonymousFilter(BeanReference authenticationFilterSecurityContextHolderStrategyRef) {
807816
Element anonymousElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.ANONYMOUS);
808817
if (anonymousElt != null && "false".equals(anonymousElt.getAttribute("enabled"))) {
809818
return;
@@ -833,6 +842,8 @@ void createAnonymousFilter() {
833842
this.anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(1, username);
834843
this.anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(2,
835844
AuthorityUtils.commaSeparatedStringToAuthorityList(grantedAuthority));
845+
this.anonymousFilter.getPropertyValues().addPropertyValue("securityContextHolderStrategy",
846+
authenticationFilterSecurityContextHolderStrategyRef);
836847
this.anonymousFilter.setSource(source);
837848
RootBeanDefinition anonymousProviderBean = new RootBeanDefinition(AnonymousAuthenticationProvider.class);
838849
anonymousProviderBean.getConstructorArgumentValues().addIndexedArgumentValue(0, key);
@@ -847,14 +858,16 @@ private String createKey() {
847858
return Long.toString(random.nextLong());
848859
}
849860

850-
void createExceptionTranslationFilter() {
861+
void createExceptionTranslationFilter(BeanReference authenticationFilterSecurityContextHolderStrategyRef) {
851862
BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
852863
this.accessDeniedHandler = createAccessDeniedHandler(this.httpElt, this.pc);
853864
etfBuilder.addPropertyValue("accessDeniedHandler", this.accessDeniedHandler);
854865
Assert.state(this.requestCache != null, "No request cache found");
855866
this.mainEntryPoint = selectEntryPoint();
856867
etfBuilder.addConstructorArgValue(this.mainEntryPoint);
857868
etfBuilder.addConstructorArgValue(this.requestCache);
869+
etfBuilder.addPropertyValue("securityContextHolderStrategy",
870+
authenticationFilterSecurityContextHolderStrategyRef);
858871
this.etf = etfBuilder.getBeanDefinition();
859872
}
860873

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

+42
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.w3c.dom.Element;
2525

2626
import org.springframework.beans.BeanMetadataElement;
27+
import org.springframework.beans.factory.FactoryBean;
2728
import org.springframework.beans.factory.config.BeanDefinition;
2829
import org.springframework.beans.factory.config.BeanReference;
2930
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -40,6 +41,8 @@
4041
import org.springframework.security.access.vote.RoleVoter;
4142
import org.springframework.security.config.Elements;
4243
import org.springframework.security.config.http.GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory;
44+
import org.springframework.security.core.context.SecurityContextHolder;
45+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4346
import org.springframework.security.core.session.SessionRegistryImpl;
4447
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
4548
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
@@ -106,6 +109,8 @@ class HttpConfigurationBuilder {
106109

107110
private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url";
108111

112+
private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY = "security-context-holder-strategy-ref";
113+
109114
private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref";
110115

111116
private static final String ATT_SECURITY_CONTEXT_EXPLICIT_SAVE = "security-context-explicit-save";
@@ -156,6 +161,8 @@ class HttpConfigurationBuilder {
156161

157162
private BeanDefinition forceEagerSessionCreationFilter;
158163

164+
private BeanReference holderStrategyRef;
165+
159166
private BeanReference contextRepoRef;
160167

161168
private BeanReference sessionRegistryRef;
@@ -215,6 +222,7 @@ class HttpConfigurationBuilder {
215222
String createSession = element.getAttribute(ATT_CREATE_SESSION);
216223
this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED
217224
: createPolicy(createSession);
225+
createSecurityContextHolderStrategy();
218226
createForceEagerSessionCreationFilter();
219227
createDisableEncodeUrlFilter();
220228
createCsrfFilter();
@@ -294,6 +302,10 @@ static String createPath(String path, boolean lowerCase) {
294302
return lowerCase ? path.toLowerCase() : path;
295303
}
296304

305+
BeanReference getSecurityContextHolderStrategyForAuthenticationFilters() {
306+
return this.holderStrategyRef;
307+
}
308+
297309
BeanReference getSecurityContextRepositoryForAuthenticationFilters() {
298310
return (isExplicitSave()) ? this.contextRepoRef : null;
299311
}
@@ -331,11 +343,23 @@ private void createSecurityContextPersistenceFilter() {
331343
default:
332344
scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE);
333345
}
346+
scpf.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef);
334347
scpf.addConstructorArgValue(this.contextRepoRef);
335348

336349
this.securityContextPersistenceFilter = scpf.getBeanDefinition();
337350
}
338351

352+
private void createSecurityContextHolderStrategy() {
353+
String holderStrategyRef = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_HOLDER_STRATEGY);
354+
if (!StringUtils.hasText(holderStrategyRef)) {
355+
BeanDefinition holderStrategyBean = BeanDefinitionBuilder
356+
.rootBeanDefinition(SecurityContextHolderStrategyFactory.class).getBeanDefinition();
357+
holderStrategyRef = this.pc.getReaderContext().generateBeanName(holderStrategyBean);
358+
this.pc.registerBeanComponent(new BeanComponentDefinition(holderStrategyBean, holderStrategyRef));
359+
}
360+
this.holderStrategyRef = new RuntimeBeanReference(holderStrategyRef);
361+
}
362+
339363
private void createSecurityContextRepository() {
340364
String repoRef = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY);
341365
if (!StringUtils.hasText(repoRef)) {
@@ -359,6 +383,7 @@ private void createSecurityContextRepository() {
359383
contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE);
360384
}
361385
}
386+
contextRepo.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef);
362387
BeanDefinition repoBean = contextRepo.getBeanDefinition();
363388
repoRef = this.pc.getReaderContext().generateBeanName(repoBean);
364389
this.pc.registerBeanComponent(new BeanComponentDefinition(repoBean, repoRef));
@@ -374,6 +399,7 @@ private boolean isDisableUrlRewriting() {
374399

375400
private void createSecurityContextHolderFilter() {
376401
BeanDefinitionBuilder filter = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderFilter.class);
402+
filter.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef);
377403
filter.addConstructorArgValue(this.contextRepoRef);
378404
this.securityContextPersistenceFilter = filter.getBeanDefinition();
379405
}
@@ -485,6 +511,7 @@ else if (StringUtils.hasText(sessionAuthStratRef)) {
485511
if (StringUtils.hasText(errorUrl)) {
486512
failureHandler.getPropertyValues().addPropertyValue("defaultFailureUrl", errorUrl);
487513
}
514+
sessionMgmtFilter.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef);
488515
sessionMgmtFilter.addPropertyValue("authenticationFailureHandler", failureHandler);
489516
sessionMgmtFilter.addConstructorArgValue(this.contextRepoRef);
490517
if (!StringUtils.hasText(sessionAuthStratRef) && sessionFixationStrategy != null && !useChangeSessionId) {
@@ -744,6 +771,7 @@ private void createFilterSecurityInterceptor(BeanReference authManager) {
744771
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
745772
}
746773
builder.addPropertyValue("securityMetadataSource", securityMds);
774+
builder.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef);
747775
BeanDefinition fsiBean = builder.getBeanDefinition();
748776
String fsiId = this.pc.getReaderContext().generateBeanName(fsiBean);
749777
this.pc.registerBeanComponent(new BeanComponentDefinition(fsiBean, fsiId));
@@ -883,4 +911,18 @@ public SecurityContextHolderAwareRequestFilter getBean() {
883911

884912
}
885913

914+
static class SecurityContextHolderStrategyFactory implements FactoryBean<SecurityContextHolderStrategy> {
915+
916+
@Override
917+
public SecurityContextHolderStrategy getObject() throws Exception {
918+
return SecurityContextHolder.getContextHolderStrategy();
919+
}
920+
921+
@Override
922+
public Class<?> getObjectType() {
923+
return SecurityContextHolderStrategy.class;
924+
}
925+
926+
}
927+
886928
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ private BeanReference createFilterChain(Element element, ParserContext pc) {
147147
httpBldr.getSecurityContextRepositoryForAuthenticationFilters();
148148
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc,
149149
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
150+
httpBldr.getSecurityContextHolderStrategyForAuthenticationFilters(),
150151
httpBldr.getSecurityContextRepositoryForAuthenticationFilters(), httpBldr.getSessionStrategy(),
151152
portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
152153
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.beans.BeanMetadataElement;
2222
import org.springframework.beans.factory.config.BeanDefinition;
23+
import org.springframework.beans.factory.config.BeanReference;
2324
import org.springframework.beans.factory.config.RuntimeBeanReference;
2425
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2526
import org.springframework.beans.factory.support.ManagedList;
@@ -61,13 +62,17 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
6162

6263
private BeanMetadataElement logoutSuccessHandler;
6364

64-
LogoutBeanDefinitionParser(String loginPageUrl, String rememberMeServices, BeanMetadataElement csrfLogoutHandler) {
65+
private BeanReference authenticationFilterSecurityContextHolderStrategyRef;
66+
67+
LogoutBeanDefinitionParser(String loginPageUrl, String rememberMeServices, BeanMetadataElement csrfLogoutHandler,
68+
BeanReference authenticationFilterSecurityContextHolderStrategyRef) {
6569
this.defaultLogoutUrl = loginPageUrl + "?logout";
6670
this.rememberMeServices = rememberMeServices;
6771
this.csrfEnabled = csrfLogoutHandler != null;
6872
if (this.csrfEnabled) {
6973
this.logoutHandlers.add(csrfLogoutHandler);
7074
}
75+
this.authenticationFilterSecurityContextHolderStrategyRef = authenticationFilterSecurityContextHolderStrategyRef;
7176
}
7277

7378
@Override
@@ -123,6 +128,8 @@ public BeanDefinition parse(Element element, ParserContext pc) {
123128
}
124129
this.logoutHandlers.add(new RootBeanDefinition(LogoutSuccessEventPublishingLogoutHandler.class));
125130
builder.addConstructorArgValue(this.logoutHandlers);
131+
builder.addPropertyValue("securityContextHolderStrategy",
132+
this.authenticationFilterSecurityContextHolderStrategyRef);
126133
return builder.getBeanDefinition();
127134
}
128135

config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc

+3
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ http.attlist &=
333333
attribute auto-config {xsd:boolean}?
334334
http.attlist &=
335335
use-expressions?
336+
http.attlist &=
337+
## A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the SecurityContextHolder is stored during a request
338+
attribute security-context-holder-strategy-ref {xsd:token}?
336339
http.attlist &=
337340
## Controls the eagerness with which an HTTP session is created by Spring Security classes. If not set, defaults to "ifRequired". If "stateless" is used, this implies that the application guarantees that it will not create a session. This differs from the use of "never" which means that Spring Security will not create a session, but will make use of one if the application does.
338341
attribute create-session {"ifRequired" | "always" | "never" | "stateless"}?

config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd

+7
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,13 @@
12241224
</xs:documentation>
12251225
</xs:annotation>
12261226
</xs:attribute>
1227+
<xs:attribute name="security-context-holder-strategy-ref" type="xs:token">
1228+
<xs:annotation>
1229+
<xs:documentation>A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the
1230+
SecurityContextHolder is stored during a request
1231+
</xs:documentation>
1232+
</xs:annotation>
1233+
</xs:attribute>
12271234
<xs:attribute name="create-session">
12281235
<xs:annotation>
12291236
<xs:documentation>Controls the eagerness with which an HTTP session is created by Spring Security classes.

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

+11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.security.config.test.SpringTestContextExtension;
3535
import org.springframework.security.core.Authentication;
3636
import org.springframework.security.core.AuthenticationException;
37+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3738
import org.springframework.security.web.FilterChainProxy;
3839
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
3940
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -45,6 +46,8 @@
4546

4647
import static org.assertj.core.api.Assertions.assertThat;
4748
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
49+
import static org.mockito.Mockito.atLeastOnce;
50+
import static org.mockito.Mockito.verify;
4851
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
4952
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5053
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -147,6 +150,14 @@ public void authenticateWhenCustomUsernameAndPasswordParametersThenSucceeds() th
147150
.andExpect(redirectedUrl("/"));
148151
}
149152

153+
@Test
154+
public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
155+
this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire();
156+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
157+
this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error"));
158+
verify(strategy, atLeastOnce()).getContext();
159+
}
160+
150161
/**
151162
* SEC-2919 - DefaultLoginGeneratingFilter incorrectly used if login-url="/login"
152163
*/

0 commit comments

Comments
 (0)