From 28145ed71352b5a182b8a208ca365e842e6d4966 Mon Sep 17 00:00:00 2001 From: DingHao Date: Wed, 8 Jan 2025 10:18:19 +0800 Subject: [PATCH 1/2] Avoid unnecessary instantiation of HttpSecurity when a SecurityFilterChain bean is provided Signed-off-by: DingHao --- .../WebSecurityConfiguration.java | 15 ++++---- .../WebSecurityConfigurationTests.java | 36 ++++++++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java index 4490852862c..f58e9e55fc0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java @@ -22,6 +22,7 @@ import jakarta.servlet.Filter; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -74,9 +75,6 @@ public class WebSecurityConfiguration implements ImportAware { private List webSecurityCustomizers = Collections.emptyList(); - @Autowired(required = false) - private HttpSecurity httpSecurity; - @Bean public static DelegatingApplicationListener delegatingApplicationListener() { return new DelegatingApplicationListener(); @@ -94,14 +92,15 @@ public SecurityExpressionHandler webSecurityExpressionHandler( * @throws Exception */ @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) - public Filter springSecurityFilterChain() throws Exception { + public Filter springSecurityFilterChain(ObjectProvider provider) throws Exception { boolean hasFilterChain = !this.securityFilterChains.isEmpty(); if (!hasFilterChain) { this.webSecurity.addSecurityFilterChainBuilder(() -> { - this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); - this.httpSecurity.formLogin(Customizer.withDefaults()); - this.httpSecurity.httpBasic(Customizer.withDefaults()); - return this.httpSecurity.build(); + HttpSecurity httpSecurity = provider.getObject(); + httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); + httpSecurity.formLogin(Customizer.withDefaults()); + httpSecurity.httpBasic(Customizer.withDefaults()); + return httpSecurity.build(); }); } for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java index f6a53bff458..a17ed62723e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -27,8 +27,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -326,6 +328,12 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT .isInstanceOf(IllegalArgumentException.class); } + @Test + public void avoidUnnecessaryHttpSecurityInstantiationWhenProvideOneSecurityFilterChain() { + this.spring.register(SecurityFilterChainConfig.class).autowire(); + assertThat(this.spring.getContext().getBean(CustomBeanPostProcessor.class).instantiationCount).isEqualTo(1); + } + private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) { Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER"); assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse(); @@ -347,6 +355,32 @@ private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEval assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue(); } + @Configuration + @EnableWebSecurity + @Import(CustomBeanPostProcessor.class) + static class SecurityFilterChainConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).build(); + } + + } + + static class CustomBeanPostProcessor implements BeanPostProcessor { + + int instantiationCount = 0; + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof HttpSecurity) { + this.instantiationCount++; + } + return bean; + } + + } + @Configuration @EnableWebSecurity @Import(AuthenticationTestConfiguration.class) From b21850a1345841072b02b4ac7c5ba6191b1ccec3 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:11:08 -0600 Subject: [PATCH 2/2] CustomBeanPostProcessor -> CountHttpSecurityBeanPostProcessor Issue gh-16370 Signed-off-by: Rob Winch <362503+rwinch@users.noreply.github.com> --- .../web/configuration/WebSecurityConfigurationTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java index a17ed62723e..e546ffb6a13 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java @@ -331,7 +331,8 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT @Test public void avoidUnnecessaryHttpSecurityInstantiationWhenProvideOneSecurityFilterChain() { this.spring.register(SecurityFilterChainConfig.class).autowire(); - assertThat(this.spring.getContext().getBean(CustomBeanPostProcessor.class).instantiationCount).isEqualTo(1); + assertThat(this.spring.getContext().getBean(CountHttpSecurityBeanPostProcessor.class).instantiationCount) + .isEqualTo(1); } private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) { @@ -357,7 +358,7 @@ private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEval @Configuration @EnableWebSecurity - @Import(CustomBeanPostProcessor.class) + @Import(CountHttpSecurityBeanPostProcessor.class) static class SecurityFilterChainConfig { @Bean @@ -367,7 +368,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } - static class CustomBeanPostProcessor implements BeanPostProcessor { + static class CountHttpSecurityBeanPostProcessor implements BeanPostProcessor { int instantiationCount = 0;