Skip to content

Make it easier to determine where a filter chain has been defined #15874

Closed
@wilkinsona

Description

@wilkinsona

Expected Behavior

When there a multiple filter chains configured for any request, Spring Security should make it as easy as possible for the user to correct their configuration mistake by clearly identifying the filter chains that are involved.

Current Behavior

An app fails to start with an exception like this:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain': Cannot create inner bean '(inner bean)#7ddd84b5' while setting constructor argument with key [1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBeanValue(BeanDefinitionValueResolver.java:421) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.lambda$resolveValueIfNecessary$1(BeanDefinitionValueResolver.java:153) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:262) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:152) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:460) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:191) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:691) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:206) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1371) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1208) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:288) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:312) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1122) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1093) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1030) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:755) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:442) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1364) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1353) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]
        at com.example.security.oauth2authorizationserver.OAuth2AuthorizationServerApplication.main(OAuth2AuthorizationServerApplication.java:31) ~[main/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#7ddd84b5' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Failed to instantiate [jakarta.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception with message: A filter chain that matches any request has already been configured, which means that this filter chain [DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@25f15f50, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@52b46d52, org.springframework.security.web.context.SecurityContextHolderFilter@2b4b96a4, org.springframework.security.web.header.HeaderWriterFilter@3252747e, org.springframework.security.web.csrf.CsrfFilter@631cb129, org.springframework.security.web.authentication.logout.LogoutFilter@2bfaba70, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@b67cc70, DefaultResourcesFilter [matcher=Ant [pattern='/default-ui.css', GET], resource=org/springframework/security/default-ui.css], org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@36b9cb99, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7327a447, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2dd8ff1d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@17e9bc9e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@67022ea, org.springframework.security.web.access.ExceptionTranslationFilter@3d20e575, org.springframework.security.web.access.intercept.AuthorizationFilter@68d6d775]]] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1351) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1181) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBeanValue(BeanDefinitionValueResolver.java:407) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        ... 29 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [jakarta.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception with message: A filter chain that matches any request has already been configured, which means that this filter chain [DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@25f15f50, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@52b46d52, org.springframework.security.web.context.SecurityContextHolderFilter@2b4b96a4, org.springframework.security.web.header.HeaderWriterFilter@3252747e, org.springframework.security.web.csrf.CsrfFilter@631cb129, org.springframework.security.web.authentication.logout.LogoutFilter@2bfaba70, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@b67cc70, DefaultResourcesFilter [matcher=Ant [pattern='/default-ui.css', GET], resource=org/springframework/security/default-ui.css], org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@36b9cb99, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7327a447, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2dd8ff1d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@17e9bc9e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@67022ea, org.springframework.security.web.access.ExceptionTranslationFilter@3d20e575, org.springframework.security.web.access.intercept.AuthorizationFilter@68d6d775]]] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[spring-core-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[spring-core-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        ... 35 common frames omitted
Caused by: java.lang.IllegalArgumentException: A filter chain that matches any request has already been configured, which means that this filter chain [DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@25f15f50, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@52b46d52, org.springframework.security.web.context.SecurityContextHolderFilter@2b4b96a4, org.springframework.security.web.header.HeaderWriterFilter@3252747e, org.springframework.security.web.csrf.CsrfFilter@631cb129, org.springframework.security.web.authentication.logout.LogoutFilter@2bfaba70, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@b67cc70, DefaultResourcesFilter [matcher=Ant [pattern='/default-ui.css', GET], resource=org/springframework/security/default-ui.css], org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@36b9cb99, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7327a447, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2dd8ff1d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@17e9bc9e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@67022ea, org.springframework.security.web.access.ExceptionTranslationFilter@3d20e575, org.springframework.security.web.access.intercept.AuthorizationFilter@68d6d775]]] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.
        at org.springframework.util.Assert.isTrue(Assert.java:115) ~[spring-core-6.2.0-RC1.jar:6.2.0-RC1]
        at org.springframework.security.config.annotation.web.builders.WebSecurity.performBuild(WebSecurity.java:308) ~[spring-security-config-6.4.0-SNAPSHOT.jar:6.4.0-SNAPSHOT]
        at org.springframework.security.config.annotation.web.builders.WebSecurity.performBuild(WebSecurity.java:94) ~[spring-security-config-6.4.0-SNAPSHOT.jar:6.4.0-SNAPSHOT]
        at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:333) ~[spring-security-config-6.4.0-SNAPSHOT.jar:6.4.0-SNAPSHOT]
        at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) ~[spring-security-config-6.4.0-SNAPSHOT.jar:6.4.0-SNAPSHOT]
        at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain(WebSecurityConfiguration.java:121) ~[spring-security-config-6.4.0-SNAPSHOT.jar:6.4.0-SNAPSHOT]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.0-RC1.jar:6.2.0-RC1]
        ... 40 common frames omitted

For me, describing a filter chain purely in terms of the filters that it contains isn't as helpful as it could be. Thanks to the DSL, the specific filters and their class names are an implementation detail. I find it difficult to map the list of 10+ filters back to a particular piece of configuration where the problematic filter chain was defined and I'd like Spring Security to do that for me. Perhaps it could provide some origin information (the name of the bean?) where each filter chain that's involved in the problem was defined?

Context

I found this while trying to adapt to the deprecation of OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) in the AOT smoke test for authorization server:

diff --git a/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java b/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java
index d9eb0720..bf5967ae 100644
--- a/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java
+++ b/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java
@@ -48,6 +48,7 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryR
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
 import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
 import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -67,8 +68,9 @@ public class OAuth2AuthorizationServerSecurityConfiguration {
        @Bean
        @Order(1)
        public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
-               OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
-               return http.formLogin(withDefaults()).build();
+               return http.with(OAuth2AuthorizationServerConfigurer.authorizationServer(), Customizer.withDefaults())
+                       .formLogin(withDefaults())
+                       .build();
        }
 
        @Bean

The above was my first attempt at following the advice in the deprecation notice. After looking at the code that was deprecated and what it does, it would appear that the following is what was needed in this case:

diff --git a/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java b/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java
index d9eb0720..eb743932 100644
--- a/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java
+++ b/security/security-oauth2-authorization-server/src/main/java/com/example/security/oauth2authorizationserver/OAuth2AuthorizationServerSecurityConfiguration.java
@@ -48,6 +48,7 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryR
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
 import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
 import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -67,8 +68,12 @@ public class OAuth2AuthorizationServerSecurityConfiguration {
        @Bean
        @Order(1)
        public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
-               OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
-               return http.formLogin(withDefaults()).build();
+               OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer
+                       .authorizationServer();
+               return http.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
+                       .with(authorizationServerConfigurer, Customizer.withDefaults())
+                       .formLogin(withDefaults())
+                       .build();
        }
 
        @Bean

Metadata

Metadata

Assignees

Labels

in: configAn issue in spring-security-configtype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions