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
Comment From: sjohnr
Thanks for reporting this Andy. I'll bring this to the team's attention.
Comment From: jgrandja
Thanks for the feedback @wilkinsona. Other than specifying the origin (e.g. Bean name) is there anything else you can think of that could help improve/expedite the troubleshooting of multiple "any request" filter chain issue?
Comment From: wilkinsona
If you created a specific exception type for it, Boot could provide a FailureAnalyzer for that exception. That would allow Boot to suppress (by default) the logging of the exception and its verbose stack trace in favor of a more friendly description of the problem and some suggestions on how to fix it.
The only other thing that I can think of is to perhaps reconsider including all of the fully-qualified filter class names in the exception message. They're quite far removed from the DSL that was likely used to configure them which makes me wonder how helpful they are. Perhaps a short description of each could be provided instead? For example, org.springframework.security.web.csrf.CsrfFilter@631cb129 could be just CSRF without really losing any information.
Comment From: jgrandja
Thanks @wilkinsona. Great information. We'll look at these different options and provide an improvement.