Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wilkinsona opened this issue Oct 4, 2024 · 1 comment
Open
Assignees
Labels
for: team-attention This ticket should be discussed as a team before proceeding in: config An issue in spring-security-config type: enhancement A general enhancement

Comments

@wilkinsona
Copy link
Member

wilkinsona commented Oct 4, 2024

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
@wilkinsona wilkinsona added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Oct 4, 2024
@sjohnr sjohnr added in: config An issue in spring-security-config and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 4, 2024
@sjohnr
Copy link
Member

sjohnr commented Oct 4, 2024

Thanks for reporting this Andy. I'll bring this to the team's attention.

@sjohnr sjohnr added the for: team-attention This ticket should be discussed as a team before proceeding label Oct 4, 2024
@sjohnr sjohnr self-assigned this Oct 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: team-attention This ticket should be discussed as a team before proceeding in: config An issue in spring-security-config type: enhancement A general enhancement
Projects
Status: No status
Development

No branches or pull requests

2 participants