Describe the bug Adding a filter relative (before/after) to a custom defined filter added previously does not work since spring security 5.5.0.

To Reproduce Clone this minimal example project. Run

./gradlew run

and see that it breaks (works when downgrading spring boot to 2.4.5).

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.7.jar:5.3.7]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.7.jar:5.3.7]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:438) ~[spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:337) ~[spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1336) ~[spring-boot-2.5.0.jar:2.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1325) ~[spring-boot-2.5.0.jar:2.5.0]
    at de.selfenergy.debug.spring.security.filters.FiltersApplication.main(FiltersApplication.java:10) ~[main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.7.jar:5.3.7]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.7.jar:5.3.7]
    ... 21 common frames omitted
Caused by: java.lang.NullPointerException: null
    at org.springframework.security.config.annotation.web.builders.HttpSecurity.addFilterAtOffsetOf(HttpSecurity.java:2654) ~[spring-security-config-5.5.0.jar:5.5.0]
    at org.springframework.security.config.annotation.web.builders.HttpSecurity.addFilterAfter(HttpSecurity.java:2645) ~[spring-security-config-5.5.0.jar:5.5.0]
    at de.selfenergy.debug.spring.security.filters.SecurityConfiguration.configure(SecurityConfiguration.java:34) ~[main/:na]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.getHttp(WebSecurityConfigurerAdapter.java:217) ~[spring-security-config-5.5.0.jar:5.5.0]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.init(WebSecurityConfigurerAdapter.java:315) ~[spring-security-config-5.5.0.jar:5.5.0]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.init(WebSecurityConfigurerAdapter.java:93) ~[spring-security-config-5.5.0.jar:5.5.0]
    at de.selfenergy.debug.spring.security.filters.SecurityConfiguration$$EnhancerBySpringCGLIB$$1613d8bd.init(<generated>) ~[main/:na]
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.init(AbstractConfiguredSecurityBuilder.java:338) ~[spring-security-config-5.5.0.jar:5.5.0]
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:300) ~[spring-security-config-5.5.0.jar:5.5.0]
    at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) ~[spring-security-config-5.5.0.jar:5.5.0]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain(WebSecurityConfiguration.java:127) ~[spring-security-config-5.5.0.jar:5.5.0]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[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:566) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.7.jar:5.3.7]
    ... 22 common frames omitted

Relevant code lines:

http.addFilterAfter(new SpringRelativeFilter(), SecurityContextHolderAwareRequestFilter.class)
       .addFilterAfter(new CustomRelativeFilter(), SpringRelativeFilter.class);

Expected behavior Adding filters relative to custom defined ones should work.

Sample minimal example project

If this is indeed a bug and you are interested I would offer to prepare a PR to adjust the behaviour :)

Related: gh-9633

Comment From: whcrow

+1

Spring Security Adding filters relative to custom ones is broken

Comment From: austinarbor

As a temporary workaround, it looks like specifying the same afterFilter for both custom filters should achieve the same result as 5.4.x. Spring's OrderComparator is seemingly documented as unstable but, as of this writing the underlying data structure used to sort the filters is an ArrayList, which has a stable sort when called https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java#L2619

So until this is resolved, the below code should work

http.addFilterAfter(new SpringRelativeFilter(), SecurityContextHolderAwareRequestFilter.class)
       .addFilterAfter(new CustomRelativeFilter(), SecurityContextHolderAwareRequestFilter.class);

Comment From: z0mb1ek

same here with Keycloak spring security adapter

Comment From: ngergs

The Keycloak spring security adapter can be fixed using the workaround from @austinarbor. I added a keycloak branch to the minimal example project where this is demonstrated :)

Comment From: z0mb1ek

sure, I wanted to point out the importance of the bug

Comment From: Stexxen

Found this issue after running a CI test build with Spring Boot 2.5.0 and all our security filter related tests failed. Thought I'd add another example :

Resolving was as per @austinarbor to only use Filters that come pre-supplied. From

.addFilterAfter(new DeviceAuthenticationFilter(authenticationManager), BasicAuthenticationFilter.class)
.addFilterBefore(wrappingFilter, DeviceAuthenticationFilter.class)

to

.addFilterAfter(new DeviceAuthenticationFilter(authenticationManager), BasicAuthenticationFilter.class)
.addFilterAfter(wrappingFilter, BasicAuthenticationFilter.class)

Executing in JDK 15 also give more info about the specifics of the NPE, but nothing that @whcrow hasn't already supplied

Of the other 100's of tests no others failed, so testamount to the excellent Spring devs that this is only issue experienced after such a big change.

Comment From: whcrow

Found this issue after running a CI test build with Spring Boot 2.5.0 and all our security filter related tests failed. Thought I'd add another example :

Resolving was as per @austinarbor to only use Filters that come pre-supplied. From

java .addFilterAfter(new DeviceAuthenticationFilter(authenticationManager), BasicAuthenticationFilter.class) .addFilterBefore(wrappingFilter, DeviceAuthenticationFilter.class)

to

java .addFilterAfter(new DeviceAuthenticationFilter(authenticationManager), BasicAuthenticationFilter.class) .addFilterAfter(wrappingFilter, BasicAuthenticationFilter.class)

Executing in JDK 15 also give more info about the specifics of the NPE, but nothing that @whcrow hasn't already supplied

Of the other 100's of tests no others failed, so testamount to the excellent Spring devs that this is only issue experienced after such a big change.

@Stexxen Under jdk11 + springboot 2.4.6, It works fine. But in springboot 2.5.0, A NullPointerException is triggered. According to my test, The second parameter of addFilterBefore only supports built-in filters, such as UsernamePasswordAuthenticationFilter, custom filters are not supported.

Comment From: mnhock

Some here by using the KeycloakWebSecurityConfigurerAdapter and updating to Spring Boot 2.5.0 with its Spring Security 5.5 transitive dependency.

Comment From: thocou

An issue has been opened on Keycloak side too: https://issues.redhat.com/browse/KEYCLOAK-18283

Comment From: theexiile1305

Added a pull request for fixing this issue #9832

Comment From: adhirajsinghchauhan

Culprit: a31a855146d7485a9e30cdb70a18be212e4d008f. That commit includes tests, but none of those tests operate on custom filters — so only filters "hardcoded" in FilterOrderRegistration will work as anchors for configuring before/after filters. However, custom filters are never added into the backing filterToOrder map (in FilterOrderRegistration), which is what causes the NPE at L2654: https://github.com/spring-projects/spring-security/blob/68f91edbb8a223ee8505893697d85cbe51890ea2/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java#L143

https://github.com/spring-projects/spring-security/blob/68f91edbb8a223ee8505893697d85cbe51890ea2/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java#L2653-L2657

Comment From: ch4mpy

Created a PR with - a test validating custom filter can be added before or after another custom filter - a proposal for a fix having this test pass

P.S. If anyone has a hint on how to configure Eclipse STS or Visual Studio Code, import Spring-security projects and run unit-tests from one of those IDEs, this would save me a lot of time :/

Comment From: marcusdacoregio

Thanks to everyone that contributed to this issue. The fix was merged into the main branch and backported to 5.5.x, 5.4.x, 5.3.x, and 5.2.x branches.

@selfenergy, @whcrow, @austinarbor, @z0mb1ek, would you please test those changes in your applications by using the 5.5.1-SNAPSHOT version of Spring Security? It would be good if you folks could do it 😄.

Comment From: z0mb1ek

@marcusdacoregio works like a charm, thx

Comment From: whcrow

@marcusdacoregio

works with 5.5.1-SNAPSHOT, Thanks 👏

Comment From: ngergs

@marcusdacoregio Works perfekt. Can also confirm that both filter are called in the correct order. Thanks! :+1:

Comment From: jomu78

@marcusdacoregio: works with 5.5.1-SNAPSHOT connected to Keycloak

Comment From: bgarciaentornos

Hi, I've discovered what seems like a very similar error case to this one that hasn't been addressed yet (as far as I know). It happens when adding ONLY a custom filter using the addFilterAfter method, the NPE is the same as before this fix. For example, if you comment the first filter on @ngergs little project you'd get that. Any help with this?

Thanks a lot and regards.

Comment From: marcusdacoregio

Hi, @bgarciaentornos.

if you comment the first filter on @ngergs little project you'd get that

If you comment that line of code, the first filter will never get registered, therefore the NPE is expected in this scenario. If you think it is really a bug, please create a minimal, reproducible sample and open a new issue. Thank you!

Comment From: bgarciaentornos

Yep, sorry, it wasn't exactly like that. Our case is with a OAuth2ClientContextFilter being registered as a Bean. We're going to check it again and open an issue if we find it reproducible.

Thanks for your help.