diff --git a/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java b/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java index 0870fe2da6..2c51a8e6d8 100644 --- a/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java +++ b/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java @@ -47,6 +47,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -111,6 +112,9 @@ public abstract class AbstractSecurityInterceptor protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private ApplicationEventPublisher eventPublisher; private AccessDecisionManager accessDecisionManager; @@ -196,7 +200,7 @@ public abstract class AbstractSecurityInterceptor publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } - if (SecurityContextHolder.getContext().getAuthentication() == null) { + if (this.securityContextHolderStrategy.getContext().getAuthentication() == null) { credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } @@ -216,10 +220,10 @@ public abstract class AbstractSecurityInterceptor // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs != null) { - SecurityContext origCtx = SecurityContextHolder.getContext(); - SecurityContext newCtx = SecurityContextHolder.createEmptyContext(); + SecurityContext origCtx = this.securityContextHolderStrategy.getContext(); + SecurityContext newCtx = this.securityContextHolderStrategy.createEmptyContext(); newCtx.setAuthentication(runAs); - SecurityContextHolder.setContext(newCtx); + this.securityContextHolderStrategy.setContext(newCtx); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs)); @@ -229,7 +233,7 @@ public abstract class AbstractSecurityInterceptor } this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null"); // no further work post-invocation - return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); + return new InterceptorStatusToken(this.securityContextHolderStrategy.getContext(), false, attributes, object); } @@ -260,7 +264,7 @@ public abstract class AbstractSecurityInterceptor */ protected void finallyInvocation(InterceptorStatusToken token) { if (token != null && token.isContextHolderRefreshRequired()) { - SecurityContextHolder.setContext(token.getSecurityContext()); + this.securityContextHolderStrategy.setContext(token.getSecurityContext()); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.of( () -> "Reverted to original authentication " + token.getSecurityContext().getAuthentication())); @@ -305,7 +309,7 @@ public abstract class AbstractSecurityInterceptor * @return an authenticated Authentication object. */ private Authentication authenticateIfRequired() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication.isAuthenticated() && !this.alwaysReauthenticate) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Did not re-authenticate %s before authorizing", authentication)); @@ -317,9 +321,9 @@ public abstract class AbstractSecurityInterceptor if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Re-authenticated %s before authorizing", authentication)); } - SecurityContext context = SecurityContextHolder.createEmptyContext(); + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authentication); - SecurityContextHolder.setContext(context); + this.securityContextHolderStrategy.setContext(context); return authentication; } @@ -378,6 +382,17 @@ public abstract class AbstractSecurityInterceptor public abstract SecurityMetadataSource obtainSecurityMetadataSource(); + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) { this.accessDecisionManager = accessDecisionManager; } diff --git a/core/src/test/java/org/springframework/security/core/context/ListeningSecurityContextHolderStrategyTests.java b/core/src/test/java/org/springframework/security/core/context/ListeningSecurityContextHolderStrategyTests.java index f84cfbc43a..46f6a60b4e 100644 --- a/core/src/test/java/org/springframework/security/core/context/ListeningSecurityContextHolderStrategyTests.java +++ b/core/src/test/java/org/springframework/security/core/context/ListeningSecurityContextHolderStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/etc/checkstyle/checkstyle.xml b/etc/checkstyle/checkstyle.xml index e8ea50e0d6..ad5192b10f 100644 --- a/etc/checkstyle/checkstyle.xml +++ b/etc/checkstyle/checkstyle.xml @@ -15,6 +15,7 @@ + diff --git a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java index fe80b492cb..5204f33a39 100644 --- a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java +++ b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java @@ -33,6 +33,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.firewall.FirewalledRequest; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler; @@ -145,6 +146,9 @@ public class FilterChainProxy extends GenericFilterBean { private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED"); + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private List filterChains; private FilterChainValidator filterChainValidator = new NullFilterChainValidator(); @@ -185,7 +189,7 @@ public class FilterChainProxy extends GenericFilterBean { this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex); } finally { - SecurityContextHolder.clearContext(); + this.securityContextHolderStrategy.clearContext(); request.removeAttribute(FILTER_APPLIED); } } @@ -246,6 +250,17 @@ public class FilterChainProxy extends GenericFilterBean { return Collections.unmodifiableList(this.filterChains); } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + /** * Used (internally) to specify a validation strategy for the filters in each * configured chain. diff --git a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java index d870e85629..051893c640 100644 --- a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2021 the original author or authors. + * Copyright 2004-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; @@ -82,6 +83,9 @@ import org.springframework.web.filter.GenericFilterBean; */ public class ExceptionTranslationFilter extends GenericFilterBean implements MessageSourceAware { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); private AuthenticationEntryPoint authenticationEntryPoint; @@ -183,7 +187,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean implements Mes private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AccessDeniedException exception) throws ServletException, IOException { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication); if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) { if (logger.isTraceEnabled()) { @@ -209,8 +213,8 @@ public class ExceptionTranslationFilter extends GenericFilterBean implements Mes AuthenticationException reason) throws ServletException, IOException { // SEC-112: Clear the SecurityContextHolder's Authentication, as the // existing Authentication is no longer considered valid - SecurityContext context = SecurityContextHolder.createEmptyContext(); - SecurityContextHolder.setContext(context); + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); + this.securityContextHolderStrategy.setContext(context); this.requestCache.saveRequest(request, response); this.authenticationEntryPoint.commence(request, response, reason); } @@ -239,6 +243,17 @@ public class ExceptionTranslationFilter extends GenericFilterBean implements Mes this.messages = new MessageSourceAccessor(messageSource); } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + /** * Default implementation of ThrowableAnalyzer which is capable of also * unwrapping ServletExceptions. diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java b/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java index 5d00d57292..6fd89a4f10 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java @@ -34,6 +34,7 @@ import org.springframework.security.authorization.event.AuthorizationDeniedEvent import org.springframework.security.authorization.event.AuthorizationGrantedEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; @@ -46,6 +47,9 @@ import org.springframework.web.filter.OncePerRequestFilter; */ public class AuthorizationFilter extends OncePerRequestFilter { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private final AuthorizationManager authorizationManager; private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish; @@ -73,8 +77,19 @@ public class AuthorizationFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + private Authentication getAuthentication() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { throw new AuthenticationCredentialsNotFoundException( "An Authentication object was not found in the SecurityContext"); diff --git a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java index e7abefa6fd..df7d7bec51 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java @@ -40,6 +40,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.context.NullSecurityContextRepository; @@ -114,6 +115,9 @@ import org.springframework.web.filter.GenericFilterBean; public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + protected ApplicationEventPublisher eventPublisher; protected AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); @@ -315,9 +319,9 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt */ protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { - SecurityContext context = SecurityContextHolder.createEmptyContext(); + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authResult); - SecurityContextHolder.setContext(context); + this.securityContextHolderStrategy.setContext(context); this.securityContextRepository.saveContext(context, request, response); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult)); @@ -342,7 +346,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt */ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { - SecurityContextHolder.clearContext(); + this.securityContextHolderStrategy.clearContext(); this.logger.trace("Failed to process authentication request", failed); this.logger.trace("Cleared SecurityContextHolder"); this.logger.trace("Handling authentication failure"); @@ -452,6 +456,17 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt this.securityContextRepository = securityContextRepository; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + protected AuthenticationSuccessHandler getSuccessHandler() { return this.successHandler; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.java index 3b1c628842..ea141bf0da 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.java @@ -34,6 +34,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.Assert; import org.springframework.web.filter.GenericFilterBean; @@ -46,6 +47,9 @@ import org.springframework.web.filter.GenericFilterBean; */ public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); private String key; @@ -87,14 +91,14 @@ public class AnonymousAuthenticationFilter extends GenericFilterBean implements @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - if (SecurityContextHolder.getContext().getAuthentication() == null) { + if (this.securityContextHolderStrategy.getContext().getAuthentication() == null) { Authentication authentication = createAuthentication((HttpServletRequest) req); - SecurityContext context = SecurityContextHolder.createEmptyContext(); + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authentication); - SecurityContextHolder.setContext(context); + this.securityContextHolderStrategy.setContext(context); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.of(() -> "Set SecurityContextHolder to " - + SecurityContextHolder.getContext().getAuthentication())); + + this.securityContextHolderStrategy.getContext().getAuthentication())); } else { this.logger.debug("Set SecurityContextHolder to anonymous SecurityContext"); @@ -103,7 +107,7 @@ public class AnonymousAuthenticationFilter extends GenericFilterBean implements else { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.of(() -> "Did not set SecurityContextHolder since already authenticated " - + SecurityContextHolder.getContext().getAuthentication())); + + this.securityContextHolderStrategy.getContext().getAuthentication())); } } chain.doFilter(req, res); @@ -122,6 +126,17 @@ public class AnonymousAuthenticationFilter extends GenericFilterBean implements this.authenticationDetailsSource = authenticationDetailsSource; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + public Object getPrincipal() { return this.principal; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java index 9f987e9197..eaa87f0e70 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java @@ -28,6 +28,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.log.LogMessage; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -52,6 +53,9 @@ import org.springframework.web.filter.GenericFilterBean; */ public class LogoutFilter extends GenericFilterBean { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private RequestMatcher logoutRequestMatcher; private final LogoutHandler handler; @@ -92,7 +96,7 @@ public class LogoutFilter extends GenericFilterBean { private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (requiresLogout(request, response)) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Logging out [%s]", auth)); } @@ -119,6 +123,17 @@ public class LogoutFilter extends GenericFilterBean { return false; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) { Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null"); this.logoutRequestMatcher = logoutRequestMatcher; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java index c014455bfa..b65e723caf 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java @@ -33,6 +33,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.NullRememberMeServices; import org.springframework.security.web.authentication.RememberMeServices; @@ -93,6 +94,9 @@ import org.springframework.web.filter.OncePerRequestFilter; */ public class BasicAuthenticationFilter extends OncePerRequestFilter { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationManager authenticationManager; @@ -170,9 +174,9 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username)); if (authenticationIsRequired(username)) { Authentication authResult = this.authenticationManager.authenticate(authRequest); - SecurityContext context = SecurityContextHolder.createEmptyContext(); + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authResult); - SecurityContextHolder.setContext(context); + this.securityContextHolderStrategy.setContext(context); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult)); } @@ -182,7 +186,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { } } catch (AuthenticationException ex) { - SecurityContextHolder.clearContext(); + this.securityContextHolderStrategy.clearContext(); this.logger.debug("Failed to process authentication request", ex); this.rememberMeServices.loginFail(request, response); onUnsuccessfulAuthentication(request, response, ex); @@ -201,7 +205,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { private boolean authenticationIsRequired(String username) { // Only reauthenticate if username doesn't match SecurityContextHolder and user // isn't authenticated (see SEC-53) - Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); + Authentication existingAuth = this.securityContextHolderStrategy.getContext().getAuthentication(); if (existingAuth == null || !existingAuth.isAuthenticated()) { return true; } @@ -242,6 +246,17 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { return this.ignoreFailure; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + public void setAuthenticationDetailsSource( AuthenticationDetailsSource authenticationDetailsSource) { this.authenticationConverter.setAuthenticationDetailsSource(authenticationDetailsSource); diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java index 27bbe07f3e..052ee25a42 100644 --- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,11 +89,14 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo protected final Log logger = LogFactory.getLog(this.getClass()); + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + /** * SecurityContext instance used to check for equality with default (unauthenticated) * content */ - private final Object contextObject = SecurityContextHolder.createEmptyContext(); + private Object contextObject = this.securityContextHolderStrategy.createEmptyContext(); private boolean allowSessionCreation = true; @@ -125,6 +128,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo if (response != null) { SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request, httpSession != null, context); + wrappedResponse.setSecurityContextHolderStrategy(this.securityContextHolderStrategy); requestResponseHolder.setResponse(wrappedResponse); requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse)); } @@ -200,7 +204,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo * @return a new SecurityContext instance. Never null. */ protected SecurityContext generateNewContext() { - return SecurityContextHolder.createEmptyContext(); + return this.securityContextHolderStrategy.createEmptyContext(); } /** @@ -236,6 +240,17 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo this.springSecurityContextKey = springSecurityContextKey; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) { + this.securityContextHolderStrategy = strategy; + this.contextObject = this.securityContextHolderStrategy.createEmptyContext(); + } + private boolean isTransient(Object object) { if (object == null) { return false; diff --git a/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java index 697ce743dd..801554edd7 100644 --- a/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java @@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; /** * @author Luke Taylor @@ -28,6 +29,9 @@ import org.springframework.security.core.context.SecurityContextHolder; */ public final class NullSecurityContextRepository implements SecurityContextRepository { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + @Override public boolean containsContext(HttpServletRequest request) { return false; @@ -35,11 +39,21 @@ public final class NullSecurityContextRepository implements SecurityContextRepos @Override public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { - return SecurityContextHolder.createEmptyContext(); + return this.securityContextHolderStrategy.createEmptyContext(); } @Override public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) { + this.securityContextHolderStrategy = strategy; + } + } diff --git a/web/src/main/java/org/springframework/security/web/context/SaveContextOnUpdateOrErrorResponseWrapper.java b/web/src/main/java/org/springframework/security/web/context/SaveContextOnUpdateOrErrorResponseWrapper.java index 71ff20291c..214ea694f4 100644 --- a/web/src/main/java/org/springframework/security/web/context/SaveContextOnUpdateOrErrorResponseWrapper.java +++ b/web/src/main/java/org/springframework/security/web/context/SaveContextOnUpdateOrErrorResponseWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,9 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.util.OnCommittedResponseWrapper; +import org.springframework.util.Assert; /** * Base class for response wrappers which encapsulate the logic for storing a security @@ -46,6 +48,9 @@ import org.springframework.security.web.util.OnCommittedResponseWrapper; @Deprecated public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends OnCommittedResponseWrapper { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private boolean contextSaved = false; // See SEC-1052 @@ -62,6 +67,17 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends OnCommit this.disableUrlRewriting = disableUrlRewriting; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + /** * Invoke this method to disable automatic saving of the {@link SecurityContext} when * the {@link HttpServletResponse} is committed. This can be useful in the event that @@ -85,7 +101,7 @@ public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends OnCommit */ @Override protected void onResponseCommitted() { - saveContext(SecurityContextHolder.getContext()); + saveContext(this.securityContextHolderStrategy.getContext()); this.contextSaved = true; } diff --git a/web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java b/web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java index 05e3fd493f..e4a8f4eb11 100644 --- a/web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java +++ b/web/src/main/java/org/springframework/security/web/context/SecurityContextHolderFilter.java @@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; @@ -44,6 +45,9 @@ public class SecurityContextHolderFilter extends OncePerRequestFilter { private final SecurityContextRepository securityContextRepository; + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private boolean shouldNotFilterErrorDispatch; /** @@ -60,11 +64,11 @@ public class SecurityContextHolderFilter extends OncePerRequestFilter { throws ServletException, IOException { SecurityContext securityContext = this.securityContextRepository.loadContext(request).get(); try { - SecurityContextHolder.setContext(securityContext); + this.securityContextHolderStrategy.setContext(securityContext); filterChain.doFilter(request, response); } finally { - SecurityContextHolder.clearContext(); + this.securityContextHolderStrategy.clearContext(); } } @@ -73,6 +77,17 @@ public class SecurityContextHolderFilter extends OncePerRequestFilter { return this.shouldNotFilterErrorDispatch; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + /** * Disables {@link SecurityContextHolderFilter} for error dispatch. * @param shouldNotFilterErrorDispatch if the Filter should be disabled for error diff --git a/web/src/main/java/org/springframework/security/web/context/SecurityContextPersistenceFilter.java b/web/src/main/java/org/springframework/security/web/context/SecurityContextPersistenceFilter.java index c60e6bf45a..76845464d7 100644 --- a/web/src/main/java/org/springframework/security/web/context/SecurityContextPersistenceFilter.java +++ b/web/src/main/java/org/springframework/security/web/context/SecurityContextPersistenceFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ import jakarta.servlet.http.HttpSession; import org.springframework.core.log.LogMessage; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.util.Assert; import org.springframework.web.filter.GenericFilterBean; /** @@ -66,6 +68,9 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean { private SecurityContextRepository repo; + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private boolean forceEagerSessionCreation = false; public SecurityContextPersistenceFilter() { @@ -99,7 +104,7 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean { HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder); try { - SecurityContextHolder.setContext(contextBeforeChainExecution); + this.securityContextHolderStrategy.setContext(contextBeforeChainExecution); if (contextBeforeChainExecution.getAuthentication() == null) { logger.debug("Set SecurityContextHolder to empty SecurityContext"); } @@ -112,9 +117,9 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean { chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { - SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); + SecurityContext contextAfterChainExecution = this.securityContextHolderStrategy.getContext(); // Crucial removal of SecurityContextHolder contents before anything else. - SecurityContextHolder.clearContext(); + this.securityContextHolderStrategy.clearContext(); this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); this.logger.debug("Cleared SecurityContextHolder to complete request"); @@ -125,4 +130,15 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean { this.forceEagerSessionCreation = forceEagerSessionCreation; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + } diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java index 3f0baa6ebb..0bae10f2cf 100644 --- a/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java @@ -28,7 +28,9 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.stereotype.Controller; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -88,6 +90,9 @@ import org.springframework.web.method.support.ModelAndViewContainer; */ public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private ExpressionParser parser = new SpelExpressionParser(); private BeanResolver beanResolver; @@ -100,7 +105,7 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { return null; } @@ -132,6 +137,17 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet this.beanResolver = beanResolver; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + /** * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}. * @param annotationClass the class of the {@link Annotation} to find on the diff --git a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java index 3cae4cf4d1..01ec2059d7 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.session.SessionAuthenticationException; @@ -53,6 +54,9 @@ public class SessionManagementFilter extends GenericFilterBean { static final String FILTER_APPLIED = "__spring_security_session_mgmt_filter_applied"; + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + private final SecurityContextRepository securityContextRepository; private SessionAuthenticationStrategy sessionAuthenticationStrategy; @@ -89,7 +93,7 @@ public class SessionManagementFilter extends GenericFilterBean { } request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (!this.securityContextRepository.containsContext(request)) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication != null && !this.trustResolver.isAnonymous(authentication)) { // The user has been authenticated during the current request, so call the // session strategy @@ -99,14 +103,15 @@ public class SessionManagementFilter extends GenericFilterBean { catch (SessionAuthenticationException ex) { // The session strategy can reject the authentication this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", ex); - SecurityContextHolder.clearContext(); + this.securityContextHolderStrategy.clearContext(); this.failureHandler.onAuthenticationFailure(request, response, ex); return; } // Eagerly save the security context to make it available for any possible // re-entrant requests which may occur before the current request // completes. SEC-1396. - this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response); + this.securityContextRepository.saveContext(this.securityContextHolderStrategy.getContext(), request, + response); } else { // No security context or authentication present. Check for a session @@ -160,4 +165,15 @@ public class SessionManagementFilter extends GenericFilterBean { this.trustResolver = trustResolver; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + } diff --git a/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java b/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java index 30869ec541..cd272305cb 100644 --- a/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java +++ b/web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.firewall.FirewalledRequest; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.RequestRejectedException; @@ -197,6 +198,15 @@ public class FilterChainProxyTests { assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); } + @Test + public void doFilterWhenCustomSecurityContextHolderStrategyClearsSecurityContext() throws Exception { + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + this.fcp.setSecurityContextHolderStrategy(strategy); + given(this.matcher.matches(any(HttpServletRequest.class))).willReturn(true); + this.fcp.doFilter(this.request, this.response, this.chain); + verify(strategy).clearContext(); + } + @Test public void doFilterClearsSecurityContextHolderWithException() throws Exception { given(this.matcher.matches(any(HttpServletRequest.class))).willReturn(true); diff --git a/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java b/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java index c894432198..0b4d640236 100644 --- a/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java @@ -37,6 +37,7 @@ import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.web.util.WebUtils; @@ -71,9 +72,9 @@ public class AuthorizationFilterTests { AuthorizationFilter filter = new AuthorizationFilter(mockAuthorizationManager); TestingAuthenticationToken authenticationToken = new TestingAuthenticationToken("user", "password"); - SecurityContext securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(authenticationToken); - SecurityContextHolder.setContext(securityContext); + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + given(strategy.getContext()).willReturn(new SecurityContextImpl(authenticationToken)); + filter.setSecurityContextHolderStrategy(strategy); MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path"); MockHttpServletResponse mockResponse = new MockHttpServletResponse(); @@ -87,6 +88,7 @@ public class AuthorizationFilterTests { assertThat(authentication.get()).isEqualTo(authenticationToken); verify(mockFilterChain).doFilter(mockRequest, mockResponse); + verify(strategy).getContext(); } @Test diff --git a/web/src/test/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilterTests.java index 9028998eb1..f45788fefb 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/AnonymousAuthenticationFilterTests.java @@ -34,11 +34,17 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.security.core.context.SecurityContextImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** * Tests {@link AnonymousAuthenticationFilter}. @@ -73,16 +79,19 @@ public class AnonymousAuthenticationFilterTests { public void testOperationWhenAuthenticationExistsInContextHolder() throws Exception { // Put an Authentication object into the SecurityContextHolder Authentication originalAuth = new TestingAuthenticationToken("user", "password", "ROLE_A"); - SecurityContextHolder.getContext().setAuthentication(originalAuth); + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + given(strategy.getContext()).willReturn(new SecurityContextImpl(originalAuth)); AnonymousAuthenticationFilter filter = new AnonymousAuthenticationFilter("qwerty", "anonymousUsername", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); + filter.setSecurityContextHolderStrategy(strategy); // Test MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI("x"); executeFilterInContainerSimulator(mock(FilterConfig.class), filter, request, new MockHttpServletResponse(), new MockFilterChain(true)); // Ensure filter didn't change our original object - assertThat(SecurityContextHolder.getContext().getAuthentication()).isEqualTo(originalAuth); + verify(strategy).getContext(); + verify(strategy, never()).setContext(any()); } @Test diff --git a/web/src/test/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilterTests.java index 9487957fa5..890dbcb576 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilterTests.java @@ -17,20 +17,28 @@ package org.springframework.security.web.authentication; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; +import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * Tests {@link UsernamePasswordAuthenticationFilter}. @@ -118,6 +126,22 @@ public class UsernamePasswordAuthenticationFilterTests { .isThrownBy(() -> filter.attemptAuthentication(request, new MockHttpServletResponse())); } + @Test + public void testSecurityContextHolderStrategyUsed() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/login"); + request.setServletPath("/login"); + request.addParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY, "rod"); + request.addParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY, "koala"); + UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter(); + filter.setAuthenticationManager(createAuthenticationManager()); + SecurityContextHolderStrategy strategy = spy(SecurityContextHolder.getContextHolderStrategy()); + filter.setSecurityContextHolderStrategy(strategy); + filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); + ArgumentCaptor captor = ArgumentCaptor.forClass(SecurityContext.class); + verify(strategy).setContext(captor.capture()); + assertThat(captor.getValue().getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); + } + /** * SEC-571 */ diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java index c6a52952bd..0315dae9f0 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -37,6 +38,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.test.web.CodecTestUtils; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.context.SecurityContextRepository; @@ -50,6 +52,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** @@ -145,6 +148,19 @@ public class BasicAuthenticationFilterTests { assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("rod"); } + @Test + public void testSecurityContextHolderStrategyUsed() throws Exception { + String token = "rod:koala"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token.getBytes())); + SecurityContextHolderStrategy strategy = spy(SecurityContextHolder.getContextHolderStrategy()); + this.filter.setSecurityContextHolderStrategy(strategy); + this.filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); + ArgumentCaptor captor = ArgumentCaptor.forClass(SecurityContext.class); + verify(strategy).setContext(captor.capture()); + assertThat(captor.getValue().getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); + } + // gh-5586 @Test public void doFilterWhenSchemeLowercaseThenCaseInsensitveMatchWorks() throws Exception { diff --git a/web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java b/web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java index f169591c0d..04e56e4ee2 100644 --- a/web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/context/SecurityContextHolderFilterTests.java @@ -32,10 +32,12 @@ import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class SecurityContextHolderFilterTests { @@ -43,6 +45,9 @@ class SecurityContextHolderFilterTests { @Mock private SecurityContextRepository repository; + @Mock + private SecurityContextHolderStrategy strategy; + @Mock private HttpServletRequest request; @@ -77,6 +82,21 @@ class SecurityContextHolderFilterTests { assertThat(SecurityContextHolder.getContext()).isEqualTo(SecurityContextHolder.createEmptyContext()); } + @Test + void doFilterThenSetsAndClearsSecurityContextHolderStrategy() throws Exception { + Authentication authentication = TestAuthentication.authenticatedUser(); + SecurityContext expectedContext = new SecurityContextImpl(authentication); + given(this.repository.loadContext(this.requestArg.capture())).willReturn(() -> expectedContext); + FilterChain filterChain = (request, response) -> { + }; + + this.filter.setSecurityContextHolderStrategy(this.strategy); + this.filter.doFilter(this.request, this.response, filterChain); + + verify(this.strategy).setContext(expectedContext); + verify(this.strategy).clearContext(); + } + @Test void shouldNotFilterErrorDispatchWhenDefault() { assertThat(this.filter.shouldNotFilterErrorDispatch()).isFalse();