From 150b81d0088cceb4c7ba8f8f3b500c011815c180 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 26 May 2022 14:20:14 -0600 Subject: [PATCH] Add SecurityContextHolderStrategy XML Configuration for Defaults Issue gh-11061 --- .../http/AuthenticationConfigBuilder.java | 33 ++++++++++----- .../config/http/HttpConfigurationBuilder.java | 42 +++++++++++++++++++ .../HttpSecurityBeanDefinitionParser.java | 1 + .../http/LogoutBeanDefinitionParser.java | 9 +++- .../security/config/spring-security-5.8.rnc | 3 ++ .../security/config/spring-security-5.8.xsd | 7 ++++ .../security/config/spring-security-6.0.rnc | 3 ++ .../security/config/spring-security-6.0.xsd | 7 ++++ .../config/http/FormLoginConfigTests.java | 11 +++++ .../config/http/NamespaceHttpBasicTests.java | 27 ++++++++++++ ...ithCustomSecurityContextHolderStrategy.xml | 37 ++++++++++++++++ .../servlet/appendix/namespace/http.adoc | 3 ++ 12 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCustomSecurityContextHolderStrategy.xml diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index 400a9b00c3..050d941b1b 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -215,6 +215,7 @@ final class AuthenticationConfigBuilder { AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc, SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager, + BeanReference authenticationFilterSecurityContextHolderStrategyRef, BeanReference authenticationFilterSecurityContextRepositoryRef, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) { this.httpElt = element; @@ -226,21 +227,22 @@ final class AuthenticationConfigBuilder { this.portMapper = portMapper; this.portResolver = portResolver; this.csrfLogoutHandler = csrfLogoutHandler; - createAnonymousFilter(); + createAnonymousFilter(authenticationFilterSecurityContextHolderStrategyRef); createRememberMeFilter(authenticationManager); - createBasicFilter(authenticationManager); + createBasicFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef); createBearerTokenAuthenticationFilter(authenticationManager); - createFormLoginFilter(sessionStrategy, authenticationManager, authenticationFilterSecurityContextRepositoryRef); + createFormLoginFilter(sessionStrategy, authenticationManager, + authenticationFilterSecurityContextHolderStrategyRef, authenticationFilterSecurityContextRepositoryRef); createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef); createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef); createX509Filter(authenticationManager); createJeeFilter(authenticationManager); - createLogoutFilter(); + createLogoutFilter(authenticationFilterSecurityContextHolderStrategyRef); createSaml2LogoutFilter(); createLoginPageFilterIfNeeded(); createUserDetailsServiceFactory(); - createExceptionTranslationFilter(); + createExceptionTranslationFilter(authenticationFilterSecurityContextHolderStrategyRef); } void createRememberMeFilter(BeanReference authenticationManager) { @@ -270,6 +272,7 @@ final class AuthenticationConfigBuilder { } void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager, + BeanReference authenticationFilterSecurityContextHolderStrategyRef, BeanReference authenticationFilterSecurityContextRepositoryRef) { Element formLoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.FORM_LOGIN); RootBeanDefinition formFilter = null; @@ -290,6 +293,8 @@ final class AuthenticationConfigBuilder { formFilter.getPropertyValues().addPropertyValue("securityContextRepository", authenticationFilterSecurityContextRepositoryRef); } + formFilter.getPropertyValues().addPropertyValue("securityContextHolderStrategy", + authenticationFilterSecurityContextHolderStrategyRef); // Id is required by login page filter this.formFilterId = this.pc.getReaderContext().generateBeanName(formFilter); this.pc.registerBeanComponent(new BeanComponentDefinition(formFilter, this.formFilterId)); @@ -436,7 +441,8 @@ final class AuthenticationConfigBuilder { } } - void createBasicFilter(BeanReference authManager) { + void createBasicFilter(BeanReference authManager, + BeanReference authenticationFilterSecurityContextHolderStrategyRef) { Element basicAuthElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.BASIC_AUTH); if (basicAuthElt == null && !this.autoConfig) { // No basic auth, do nothing @@ -464,6 +470,8 @@ final class AuthenticationConfigBuilder { } filterBuilder.addConstructorArgValue(authManager); filterBuilder.addConstructorArgValue(this.basicEntryPoint); + filterBuilder.addPropertyValue("securityContextHolderStrategy", + authenticationFilterSecurityContextHolderStrategyRef); this.basicFilter = filterBuilder.getBeanDefinition(); } @@ -604,7 +612,7 @@ final class AuthenticationConfigBuilder { } } - void createLogoutFilter() { + void createLogoutFilter(BeanReference authenticationFilterSecurityContextHolderStrategyRef) { Element logoutElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.LOGOUT); if (logoutElt != null || this.autoConfig) { String formLoginPage = this.formLoginPage; @@ -612,7 +620,8 @@ final class AuthenticationConfigBuilder { formLoginPage = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL; } LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(formLoginPage, - this.rememberMeServicesId, this.csrfLogoutHandler); + this.rememberMeServicesId, this.csrfLogoutHandler, + authenticationFilterSecurityContextHolderStrategyRef); this.logoutFilter = logoutParser.parse(logoutElt, this.pc); this.logoutHandlers = logoutParser.getLogoutHandlers(); this.logoutSuccessHandler = logoutParser.getLogoutSuccessHandler(); @@ -668,7 +677,7 @@ final class AuthenticationConfigBuilder { return this.csrfIgnoreRequestMatchers; } - void createAnonymousFilter() { + void createAnonymousFilter(BeanReference authenticationFilterSecurityContextHolderStrategyRef) { Element anonymousElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.ANONYMOUS); if (anonymousElt != null && "false".equals(anonymousElt.getAttribute("enabled"))) { return; @@ -698,6 +707,8 @@ final class AuthenticationConfigBuilder { this.anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(1, username); this.anonymousFilter.getConstructorArgumentValues().addIndexedArgumentValue(2, AuthorityUtils.commaSeparatedStringToAuthorityList(grantedAuthority)); + this.anonymousFilter.getPropertyValues().addPropertyValue("securityContextHolderStrategy", + authenticationFilterSecurityContextHolderStrategyRef); this.anonymousFilter.setSource(source); RootBeanDefinition anonymousProviderBean = new RootBeanDefinition(AnonymousAuthenticationProvider.class); anonymousProviderBean.getConstructorArgumentValues().addIndexedArgumentValue(0, key); @@ -712,7 +723,7 @@ final class AuthenticationConfigBuilder { return Long.toString(random.nextLong()); } - void createExceptionTranslationFilter() { + void createExceptionTranslationFilter(BeanReference authenticationFilterSecurityContextHolderStrategyRef) { BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class); this.accessDeniedHandler = createAccessDeniedHandler(this.httpElt, this.pc); etfBuilder.addPropertyValue("accessDeniedHandler", this.accessDeniedHandler); @@ -720,6 +731,8 @@ final class AuthenticationConfigBuilder { this.mainEntryPoint = selectEntryPoint(); etfBuilder.addConstructorArgValue(this.mainEntryPoint); etfBuilder.addConstructorArgValue(this.requestCache); + etfBuilder.addPropertyValue("securityContextHolderStrategy", + authenticationFilterSecurityContextHolderStrategyRef); this.etf = etfBuilder.getBeanDefinition(); } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 84d4a46e1c..9567b41551 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -23,6 +23,7 @@ import jakarta.servlet.ServletRequest; import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -39,6 +40,8 @@ import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.config.Elements; import org.springframework.security.config.http.GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator; import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator; @@ -105,6 +108,8 @@ class HttpConfigurationBuilder { private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url"; + private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY = "security-context-holder-strategy-ref"; + private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref"; private static final String ATT_SECURITY_CONTEXT_EXPLICIT_SAVE = "security-context-explicit-save"; @@ -155,6 +160,8 @@ class HttpConfigurationBuilder { private BeanDefinition forceEagerSessionCreationFilter; + private BeanReference holderStrategyRef; + private BeanReference contextRepoRef; private BeanReference sessionRegistryRef; @@ -214,6 +221,7 @@ class HttpConfigurationBuilder { String createSession = element.getAttribute(ATT_CREATE_SESSION); this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED : createPolicy(createSession); + createSecurityContextHolderStrategy(); createForceEagerSessionCreationFilter(); createDisableEncodeUrlFilter(); createCsrfFilter(); @@ -293,6 +301,10 @@ class HttpConfigurationBuilder { return lowerCase ? path.toLowerCase() : path; } + BeanReference getSecurityContextHolderStrategyForAuthenticationFilters() { + return this.holderStrategyRef; + } + BeanReference getSecurityContextRepositoryForAuthenticationFilters() { return (isExplicitSave()) ? this.contextRepoRef : null; } @@ -330,11 +342,23 @@ class HttpConfigurationBuilder { default: scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE); } + scpf.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef); scpf.addConstructorArgValue(this.contextRepoRef); this.securityContextPersistenceFilter = scpf.getBeanDefinition(); } + private void createSecurityContextHolderStrategy() { + String holderStrategyRef = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_HOLDER_STRATEGY); + if (!StringUtils.hasText(holderStrategyRef)) { + BeanDefinition holderStrategyBean = BeanDefinitionBuilder + .rootBeanDefinition(SecurityContextHolderStrategyFactory.class).getBeanDefinition(); + holderStrategyRef = this.pc.getReaderContext().generateBeanName(holderStrategyBean); + this.pc.registerBeanComponent(new BeanComponentDefinition(holderStrategyBean, holderStrategyRef)); + } + this.holderStrategyRef = new RuntimeBeanReference(holderStrategyRef); + } + private void createSecurityContextRepository() { String repoRef = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY); if (!StringUtils.hasText(repoRef)) { @@ -358,6 +382,7 @@ class HttpConfigurationBuilder { contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE); } } + contextRepo.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef); BeanDefinition repoBean = contextRepo.getBeanDefinition(); repoRef = this.pc.getReaderContext().generateBeanName(repoBean); this.pc.registerBeanComponent(new BeanComponentDefinition(repoBean, repoRef)); @@ -373,6 +398,7 @@ class HttpConfigurationBuilder { private void createSecurityContextHolderFilter() { BeanDefinitionBuilder filter = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderFilter.class); + filter.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef); filter.addConstructorArgValue(this.contextRepoRef); this.securityContextPersistenceFilter = filter.getBeanDefinition(); } @@ -484,6 +510,7 @@ class HttpConfigurationBuilder { if (StringUtils.hasText(errorUrl)) { failureHandler.getPropertyValues().addPropertyValue("defaultFailureUrl", errorUrl); } + sessionMgmtFilter.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef); sessionMgmtFilter.addPropertyValue("authenticationFailureHandler", failureHandler); sessionMgmtFilter.addConstructorArgValue(this.contextRepoRef); if (!StringUtils.hasText(sessionAuthStratRef) && sessionFixationStrategy != null && !useChangeSessionId) { @@ -743,6 +770,7 @@ class HttpConfigurationBuilder { builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE); } builder.addPropertyValue("securityMetadataSource", securityMds); + builder.addPropertyValue("securityContextHolderStrategy", this.holderStrategyRef); BeanDefinition fsiBean = builder.getBeanDefinition(); String fsiId = this.pc.getReaderContext().generateBeanName(fsiBean); this.pc.registerBeanComponent(new BeanComponentDefinition(fsiBean, fsiId)); @@ -882,4 +910,18 @@ class HttpConfigurationBuilder { } + static class SecurityContextHolderStrategyFactory implements FactoryBean { + + @Override + public SecurityContextHolderStrategy getObject() throws Exception { + return SecurityContextHolder.getContextHolderStrategy(); + } + + @Override + public Class getObjectType() { + return SecurityContextHolderStrategy.class; + } + + } + } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index 7d0be016ce..1bfe7811f2 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -147,6 +147,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { httpBldr.getSecurityContextRepositoryForAuthenticationFilters(); AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager, + httpBldr.getSecurityContextHolderStrategyForAuthenticationFilters(), httpBldr.getSecurityContextRepositoryForAuthenticationFilters(), httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler()); httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers()); diff --git a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java index 51d9462d5e..f7f4a4ec0d 100644 --- a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java @@ -20,6 +20,7 @@ import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; @@ -61,13 +62,17 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser { private BeanMetadataElement logoutSuccessHandler; - LogoutBeanDefinitionParser(String loginPageUrl, String rememberMeServices, BeanMetadataElement csrfLogoutHandler) { + private BeanReference authenticationFilterSecurityContextHolderStrategyRef; + + LogoutBeanDefinitionParser(String loginPageUrl, String rememberMeServices, BeanMetadataElement csrfLogoutHandler, + BeanReference authenticationFilterSecurityContextHolderStrategyRef) { this.defaultLogoutUrl = loginPageUrl + "?logout"; this.rememberMeServices = rememberMeServices; this.csrfEnabled = csrfLogoutHandler != null; if (this.csrfEnabled) { this.logoutHandlers.add(csrfLogoutHandler); } + this.authenticationFilterSecurityContextHolderStrategyRef = authenticationFilterSecurityContextHolderStrategyRef; } @Override @@ -123,6 +128,8 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser { } this.logoutHandlers.add(new RootBeanDefinition(LogoutSuccessEventPublishingLogoutHandler.class)); builder.addConstructorArgValue(this.logoutHandlers); + builder.addPropertyValue("securityContextHolderStrategy", + this.authenticationFilterSecurityContextHolderStrategyRef); return builder.getBeanDefinition(); } diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc index 1db6c30464..c2c7812b5e 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc @@ -333,6 +333,9 @@ http.attlist &= attribute auto-config {xsd:boolean}? http.attlist &= use-expressions? +http.attlist &= + ## A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the SecurityContextHolder is stored during a request + attribute security-context-holder-strategy-ref {xsd:token}? http.attlist &= ## 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. attribute create-session {"ifRequired" | "always" | "never" | "stateless"}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd index 02e9139568..38ed40b300 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd @@ -1224,6 +1224,13 @@ + + + A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the + SecurityContextHolder is stored during a request + + + Controls the eagerness with which an HTTP session is created by Spring Security classes. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc index d76c32722a..db78e82c19 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc @@ -333,6 +333,9 @@ http.attlist &= attribute auto-config {xsd:boolean}? http.attlist &= use-expressions? +http.attlist &= + ## A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the SecurityContextHolder is stored during a request + attribute security-context-holder-strategy-ref {xsd:token}? http.attlist &= ## 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. attribute create-session {"ifRequired" | "always" | "never" | "stateless"}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd index d2048ac5b7..3bf6300e88 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd @@ -1202,6 +1202,13 @@ + + + A reference to a SecurityContextHolderStrategy bean. This can be used to customize how the + SecurityContextHolder is stored during a request + + + Controls the eagerness with which an HTTP session is created by Spring Security classes. diff --git a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java index 6091c9a0b4..31ea3b8144 100644 --- a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java @@ -33,6 +33,7 @@ import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; @@ -44,6 +45,8 @@ import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -146,6 +149,14 @@ public class FormLoginConfigTests { .andExpect(redirectedUrl("/")); } + @Test + public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { + this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); + SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); + this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); + verify(strategy, atLeastOnce()).getContext(); + } + /** * SEC-2919 - DefaultLoginGeneratingFilter incorrectly used if login-url="/login" */ diff --git a/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java b/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java index 077130bf63..67ddb45ec6 100644 --- a/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java +++ b/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java @@ -31,8 +31,11 @@ import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.util.InMemoryXmlApplicationContext; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; /** * @author Rob Winch @@ -93,6 +96,30 @@ public class NamespaceHttpBasicTests { assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } + @Test + public void httpBasicCustomSecurityContextHolderStrategy() throws Exception { + // @formatter:off + loadContext("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + ""); + // @formatter:on + this.request.addHeader("Authorization", + "Basic " + Base64.getEncoder().encodeToString("user:test".getBytes("UTF-8"))); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(this.context.getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); + } + // gh-4220 @Test public void httpBasicUnauthorizedOnDefault() throws Exception { diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCustomSecurityContextHolderStrategy.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCustomSecurityContextHolderStrategy.xml new file mode 100644 index 0000000000..7e8f943870 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCustomSecurityContextHolderStrategy.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index b9c1b3c705..3b11c9872c 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -131,6 +131,9 @@ This is a more powerful alternative to <>. A request pattern can be mapped to an empty filter chain, by setting this attribute to `none`. No security will be applied and none of Spring Security's features will be available. +[[nsa-http-security-context-holder-strategy-ref]] +* **security-context-repository-ref** +Allows injection of a custom `SecurityContextHolderStrategy` into `SecurityContextPersistenceFilter`, `SecurityContextHolderFilter`, `BasicAuthenticationFilter`, `UsernamePasswordAuthenticationFilter`, `ExceptionTranslationFilter`, `LogoutFilter`, and others. [[nsa-http-security-context-explicit-save]] * **security-context-explicit-save**