Przeglądaj źródła

Add SecurityContextHolderStrategy Java Configuration for Defaults

Issue gh-11061
Josh Cummings 3 lat temu
rodzic
commit
2c09a300b6
17 zmienionych plików z 269 dodań i 12 usunięć
  1. 10 1
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java
  2. 2 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java
  3. 21 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
  4. 1 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java
  5. 1 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
  6. 2 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java
  7. 2 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java
  8. 1 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java
  9. 3 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurer.java
  10. 2 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
  11. 50 0
      config/src/test/java/org/springframework/security/config/MockSecurityContextHolderStrategy.java
  12. 54 0
      config/src/test/java/org/springframework/security/config/annotation/SecurityContextChangedListenerArgumentMatchers.java
  13. 46 0
      config/src/test/java/org/springframework/security/config/annotation/SecurityContextChangedListenerConfig.java
  14. 16 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java
  15. 17 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java
  16. 25 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java
  17. 16 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java

+ 10 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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.
@@ -24,6 +24,8 @@ import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.expression.BeanFactoryResolver;
 import org.springframework.expression.BeanResolver;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
 import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
 import org.springframework.security.web.method.annotation.CurrentSecurityContextArgumentResolver;
@@ -50,11 +52,15 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
 
 	private BeanResolver beanResolver;
 
+	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+			.getContextHolderStrategy();
+
 	@Override
 	@SuppressWarnings("deprecation")
 	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
 		AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
 		authenticationPrincipalResolver.setBeanResolver(this.beanResolver);
+		authenticationPrincipalResolver.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
 		argumentResolvers.add(authenticationPrincipalResolver);
 		argumentResolvers
 				.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
@@ -72,6 +78,9 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
 	@Override
 	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 		this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
+		if (applicationContext.getBeanNamesForType(SecurityContextHolderStrategy.class).length == 1) {
+			this.securityContextHolderStrategy = applicationContext.getBean(SecurityContextHolderStrategy.class);
+		}
 	}
 
 }

+ 2 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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.
@@ -299,6 +299,7 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
 					.getSecurityContextRepository();
 			this.authFilter.setSecurityContextRepository(securityContextRepository);
 		}
+		this.authFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		F filter = postProcess(this.authFilter);
 		http.addFilter(filter);
 	}

+ 21 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2013 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.
@@ -16,11 +16,14 @@
 
 package org.springframework.security.config.annotation.web.configurers;
 
+import org.springframework.context.ApplicationContext;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.SecurityConfigurer;
 import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.web.DefaultSecurityFilterChain;
 
 /**
@@ -32,6 +35,8 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
 public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
 		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
 
+	private SecurityContextHolderStrategy securityContextHolderStrategy;
+
 	/**
 	 * Disables the {@link AbstractHttpConfigurer} by removing it. After doing so a fresh
 	 * version of the configuration can be applied.
@@ -49,4 +54,19 @@ public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T,
 		return (T) this;
 	}
 
+	protected SecurityContextHolderStrategy getSecurityContextHolderStrategy() {
+		if (this.securityContextHolderStrategy != null) {
+			return this.securityContextHolderStrategy;
+		}
+		ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
+		String[] names = context.getBeanNamesForType(SecurityContextHolderStrategy.class);
+		if (names.length == 1) {
+			this.securityContextHolderStrategy = context.getBean(SecurityContextHolderStrategy.class);
+		}
+		else {
+			this.securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
+		}
+		return this.securityContextHolderStrategy;
+	}
+
 }

+ 1 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java

@@ -146,6 +146,7 @@ public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>>
 		}
 		if (this.authenticationFilter == null) {
 			this.authenticationFilter = new AnonymousAuthenticationFilter(getKey(), this.principal, this.authorities);
+			this.authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		}
 		this.authenticationProvider = postProcess(this.authenticationProvider);
 		http.authenticationProvider(this.authenticationProvider);

+ 1 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -86,6 +86,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
 		authorizationFilter.setAuthorizationEventPublisher(this.publisher);
 		authorizationFilter.setShouldFilterAllDispatcherTypes(this.registry.shouldFilterAllDispatcherTypes);
+		authorizationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		http.addFilter(postProcess(authorizationFilter));
 	}
 

+ 2 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 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.
@@ -187,6 +187,7 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
 				getRequestCache(http));
 		AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
 		exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
+		exceptionTranslationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
 		http.addFilter(exceptionTranslationFilter);
 	}

+ 2 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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.
@@ -199,6 +199,7 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>>
 		if (rememberMeServices != null) {
 			basicAuthenticationFilter.setRememberMeServices(rememberMeServices);
 		}
+		basicAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		basicAuthenticationFilter = postProcess(basicAuthenticationFilter);
 		http.addFilter(basicAuthenticationFilter);
 	}

+ 1 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java

@@ -329,6 +329,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
 		this.logoutHandlers.add(postProcess(new LogoutSuccessEventPublishingLogoutHandler()));
 		LogoutHandler[] handlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
 		LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
+		result.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
 		result = postProcess(result);
 		return result;

+ 3 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 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.
@@ -108,11 +108,13 @@ public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>>
 		if (this.requireExplicitSave) {
 			SecurityContextHolderFilter securityContextHolderFilter = postProcess(
 					new SecurityContextHolderFilter(securityContextRepository));
+			securityContextHolderFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 			http.addFilter(securityContextHolderFilter);
 		}
 		else {
 			SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
 					securityContextRepository);
+			securityContextFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 			SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
 			SessionCreationPolicy sessionCreationPolicy = (sessionManagement != null)
 					? sessionManagement.getSessionCreationPolicy() : null;

+ 2 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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.
@@ -370,6 +370,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
 		if (trustResolver != null) {
 			sessionManagementFilter.setTrustResolver(trustResolver);
 		}
+		sessionManagementFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		sessionManagementFilter = postProcess(sessionManagementFilter);
 		http.addFilter(sessionManagementFilter);
 		if (isConcurrentSessionControlEnabled()) {
@@ -500,7 +501,6 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
 			concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);
 			concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);
 			concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy);
-
 			RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(
 					sessionRegistry);
 			registerSessionStrategy = postProcess(registerSessionStrategy);

+ 50 - 0
config/src/test/java/org/springframework/security/config/MockSecurityContextHolderStrategy.java

@@ -0,0 +1,50 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config;
+
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
+import org.springframework.security.core.context.SecurityContextImpl;
+
+public class MockSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
+
+	private SecurityContext context;
+
+	@Override
+	public void clearContext() {
+		this.context = null;
+	}
+
+	@Override
+	public SecurityContext getContext() {
+		if (this.context == null) {
+			this.context = createEmptyContext();
+		}
+		return this.context;
+	}
+
+	@Override
+	public void setContext(SecurityContext context) {
+		this.context = context;
+	}
+
+	@Override
+	public SecurityContext createEmptyContext() {
+		return new SecurityContextImpl();
+	}
+
+}

+ 54 - 0
config/src/test/java/org/springframework/security/config/annotation/SecurityContextChangedListenerArgumentMatchers.java

@@ -0,0 +1,54 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.annotation;
+
+import org.mockito.ArgumentMatcher;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextChangedEvent;
+
+import static org.mockito.ArgumentMatchers.argThat;
+
+public final class SecurityContextChangedListenerArgumentMatchers {
+
+	public static SecurityContextChangedEvent setAuthentication(Class<? extends Authentication> authenticationClass) {
+		return argThat(new ArgumentMatcher<SecurityContextChangedEvent>() {
+			public boolean matches(SecurityContextChangedEvent event) {
+				Authentication previous = authentication(event.getOldContext());
+				Authentication next = authentication(event.getNewContext());
+				return previous == null && next != null && authenticationClass.isAssignableFrom(next.getClass());
+			}
+
+			public String toString() {
+				return "authentication set to " + authenticationClass;
+			}
+		});
+	}
+
+	private static Authentication authentication(SecurityContext context) {
+		if (context == null) {
+			return null;
+		}
+		return context.getAuthentication();
+	}
+
+	private SecurityContextChangedListenerArgumentMatchers() {
+
+	}
+
+}

+ 46 - 0
config/src/test/java/org/springframework/security/config/annotation/SecurityContextChangedListenerConfig.java

@@ -0,0 +1,46 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.annotation;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.MockSecurityContextHolderStrategy;
+import org.springframework.security.core.context.ListeningSecurityContextHolderStrategy;
+import org.springframework.security.core.context.SecurityContextChangedListener;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+@Configuration
+public class SecurityContextChangedListenerConfig {
+
+	private SecurityContextHolderStrategy strategy = new MockSecurityContextHolderStrategy();
+
+	private SecurityContextChangedListener listener = mock(SecurityContextChangedListener.class);
+
+	@Bean
+	SecurityContextHolderStrategy securityContextHolderStrategy() {
+		return spy(new ListeningSecurityContextHolderStrategy(this.strategy, this.listener));
+	}
+
+	@Bean
+	SecurityContextChangedListener securityContextChangedListener() {
+		return this.listener;
+	}
+
+}

+ 16 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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.
@@ -20,6 +20,8 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -27,13 +29,16 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.context.SecurityContextChangedListener;
 import org.springframework.security.core.userdetails.PasswordEncodedUser;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 
+import static org.mockito.Mockito.verify;
 import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -62,6 +67,16 @@ public class AnonymousConfigurerTests {
 		this.mockMvc.perform(get("/")).andExpect(content().string("principal"));
 	}
 
+	@Test
+	public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
+		this.spring.register(AnonymousPrincipalInLambdaConfig.class, SecurityContextChangedListenerConfig.class,
+				PrincipalController.class).autowire();
+		this.mockMvc.perform(get("/")).andExpect(content().string("principal"));
+		SecurityContextChangedListener listener = this.spring.getContext()
+				.getBean(SecurityContextChangedListener.class);
+		verify(listener).securityContextChanged(setAuthentication(AnonymousAuthenticationToken.class));
+	}
+
 	@Test
 	public void requestWhenAnonymousDisabledInLambdaThenRespondsWithForbidden() throws Exception {
 		this.spring.register(AnonymousDisabledInLambdaConfig.class, PrincipalController.class).autowire();

+ 17 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 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.
@@ -26,7 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -34,6 +36,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextChangedListener;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.security.web.AuthenticationEntryPoint;
@@ -47,6 +51,7 @@ import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -192,6 +197,17 @@ public class ExceptionHandlingConfigurerTests {
 				.resolveMediaTypes(any(NativeWebRequest.class));
 	}
 
+	@Test
+	public void getWhenCustomSecurityContextHolderStrategyThenUsed() throws Exception {
+		this.spring.register(SecurityContextChangedListenerConfig.class, DefaultSecurityConfig.class).autowire();
+		this.mvc.perform(get("/"));
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		verify(strategy, atLeastOnce()).getContext();
+		SecurityContextChangedListener listener = this.spring.getContext()
+				.getBean(SecurityContextChangedListener.class);
+		verify(listener).securityContextChanged(setAuthentication(AnonymousAuthenticationToken.class));
+	}
+
 	@Test
 	public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception {
 		this.spring.register(DefaultHttpConfig.class).autowire();

+ 25 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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 org.junit.jupiter.api.extension.ExtendWith;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -30,6 +32,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.config.users.AuthenticationTestConfiguration;
+import org.springframework.security.core.context.SecurityContextChangedListener;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.core.userdetails.PasswordEncodedUser;
 import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders;
 import org.springframework.security.web.PortMapper;
@@ -42,10 +46,12 @@ import org.springframework.test.web.servlet.MockMvc;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout;
 import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@@ -97,6 +103,24 @@ public class FormLoginConfigurerTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void formLoginWhenSecurityContextHolderStrategyThenUses() throws Exception {
+		this.spring.register(FormLoginConfig.class, SecurityContextChangedListenerConfig.class).autowire();
+		// @formatter:off
+		SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin()
+				.user("username", "user")
+				.password("password", "password");
+		this.mockMvc.perform(loginRequest)
+				.andExpect(status().isFound())
+				.andExpect(redirectedUrl("/"));
+		// @formatter:on
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		verify(strategy, atLeastOnce()).getContext();
+		SecurityContextChangedListener listener = this.spring.getContext()
+				.getBean(SecurityContextChangedListener.class);
+		verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class));
+	}
+
 	@Test
 	public void loginWhenFormLoginConfiguredThenHasDefaultFailureUrl() throws Exception {
 		this.spring.register(FormLoginConfig.class).autowire();

+ 16 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 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.
@@ -25,8 +25,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -35,6 +37,7 @@ import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.context.SecurityContextChangedListener;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
@@ -53,6 +56,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -133,6 +137,17 @@ public class HttpBasicConfigurerTests {
 				.andExpect(content().string("user"));
 	}
 
+	@Test
+	public void httpBasicWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
+		this.spring.register(HttpBasic.class, Users.class, Home.class, SecurityContextChangedListenerConfig.class)
+				.autowire();
+		this.mvc.perform(get("/").with(httpBasic("user", "password"))).andExpect(status().isOk())
+				.andExpect(content().string("user"));
+		SecurityContextChangedListener listener = this.spring.getContext()
+				.getBean(SecurityContextChangedListener.class);
+		verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class));
+	}
+
 	@EnableWebSecurity
 	static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {