Invoke a filter before spring security filter chain in boot - spring-boot

I have configured my filter as below but it doesn't get invoked before Spring Security Filter chain. I have set the order as zero
I'm using Spring Boot 1.3 which support setting order on filter
#Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new UrlRewriteFilter());
registrationBean.addUrlPatterns("*");
registrationBean.addInitParameter("confReloadCheckInterval", "5");
registrationBean.addInitParameter("logLevel", "DEBUG");
registrationBean.addInitParameter("confPath", "urlrewrite.xml");
registrationBean.setOrder(0);
return registrationBean;
}

application.properties
security.filter-order=5
//>spring 2.1.3
spring.security.filter-order=5
These properties change from time to time and can be found here
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#actuator-properties
I haven't done this myself but looking at the code it appears you simply need to set a property for the order of the security filter. For example in application.properties
This should order your filter before the security filter. I don't know what the implications are of changing this order as far as security goes it feels a bit risky to me. There is a discussion amongst the Spring developers about this here. It ends up in them implementing what my answer was above.
Discussion
https://github.com/spring-projects/spring-boot/issues/1640
Test showing what this property does.(search for testCustomFilterOrder())
https://github.com/spring-projects/spring-boot/blob/1.2.x/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java

The best way I have found to do this in SpringBoot 2.0 is in your Spring Security Configuration using addFilterBefore. I chose to do it before the Username Password filter as my filter was an alternative login filter
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new UrlRewriteFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/*/**").permitAll()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/**").access("hasRole('ROLE_ADMIN')");
}
}

If you don't feel like choosing an arbitrary order for the security filter chain in the application.properties file, you can set the order using the SecurityProperties.DEFAULT_FILTER_ORDER value. This is the value used by the security filter chain since it doesn't explicitly set an order value.
#Bean
public FilterRegistrationBean<MyFilter> myFilterRegistration() {
FilterRegistrationBean<MyFilter> regBean= new FilterRegistrationBean<>();
regBean.setFilter(new MyFilter());
regBean.addUrlPatterns("/*");
// Invoke MyFilter before the security filter chain
regBean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 1);
return regBean;
}

Related

How to add a custom AuthenticationDetailsSource to BearerTokenAuthenticationFilter?

The BearerTokenAuthenticationFilter uses an AuthenticationDetailsSource to build the details of an authentication request:
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
I am implicitly using the OAuth2ResourceServerConfigurer, provided by spring-security-config-5.7.2, which sadly doesn't consider a developer-defined AuthenticationDetailsSource:
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
filter.setBearerTokenResolver(bearerTokenResolver);
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter = postProcess(filter);
http.addFilter(filter);
I confirm that the BearerTokenAuthenticationFilter has the setter I need:
setAuthenticationDetailsSource()
But I am unable to find a proper and simple way of using the setter (or any other way) to use a custom AuthenticationDetailsSource for that specific filter. I am trying to avoid creating a new filter or a new configuration.
What I have tried:
Went to github to see if there are any new versions - there are none unfortunately.
Tried to autowire the spring security filter chain and directly set the AuthenticationDetailsSource for the filter, but with no success so far.
Is there someone who managed to easily set the AuthenticationDetailsSource for a BearerTokenAuthenticationFilter?
Later edit
I have posted this question as a github issue for the Spring Security team:
https://github.com/spring-projects/spring-security/issues/11655
According to jzheaux#GitHub and as pointed in the accepted answer, I successfully used an ObjectPostProcessor:
http
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(withDefaults())
.withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
#Override
public BearerTokenAuthenticationFilter postProcess(BearerTokenAuthenticationFilter object) {
object.setAuthenticationDetailsSource(myAuthenticationDetailsSource);
return object;
}
});
To set your own AuthenticationDetailsSource, create ObjectPostProcessor class, where you can use setAuthenticationDetailsSource:
public class MyObjectPostProcessor implements ObjectPostProcessor<BearerTokenAuthenticationFilter> {
#Override
public <O extends BearerTokenAuthenticationFilter> O postProcess(O filter) {
filter.setAuthenticationDetailsSource(new MyAuthenticationDetailsSource());
return filter;
}
}
Then you can set MyObjectPostProcessor when creating SecurityFilterChain configuration:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer()
.withObjectPostProcessor(new MyObjectPostProcessor());
return http.build();
}
}

Reason for #EnableWebSecurity in the configuration class

I just read answer from the another question What is the use of #EnableWebSecurity in Spring?, but i couldn't understand why we need to add #EnableWebSecurity annotation at all.
Even I remove the #EnableWebSecurity from the configuration, my application still works.
Let's assume that we are going to implement either JWT based (rest api) or simply login based mvc application. For the following configuration what i am missing?
#Component
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService userDetailsService() {
return new MyCustomUserDetailsService();
}
#Bean
public PasswsordEncoder passwsordEncoder() {
return new BrcyptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// for the jwt authentication add jwt filter etc ..
// for the login based, override default login page, error page etc..
}
}
If you are not using spring-boot but just a pure spring project , you definitely need to add #EnableWebSecurity in order to enable spring-security.
But if you are using spring-boot 2.0 +, you do not need to add it by yourself because the spring-boot auto configuration will automatically do it for you if you forget to do so. Under the cover , it is done by the WebSecurityEnablerConfiguration which its javadoc also states this behaviour as follows:
If there is a bean of type WebSecurityConfigurerAdapter, this adds the
#EnableWebSecurity annotation. This will
make sure that the annotation is present with default security
auto-configuration and also if the user adds custom security and
forgets to add the annotation.

Disable automatic registration of WebFilter

In a spring-boot 2.4 application, I have two SecurityWebFilterChains. For only one of them, I want to add some WebFilters via addFilterBefore().
#Configuration
#EnableWebFluxSecurity
class WebSecurityConfig {
#Bean
fun filter1(service: Service): WebFilter = Filter1(service)
#Bean
fun filter2(component: Component): WebFilter = Filter2(component)
#Bean
#Order(1)
fun apiSecurityConfiguration(
http: ServerHttpSecurity,
filter1: WebFilter,
filter2: WebFilter
): SecurityWebFilterChain = http
.securityMatcher(pathMatchers("/path/**"))
.addFilterBefore(filter1, SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterAt(filter2, SecurityWebFiltersOrder.AUTHENTICATION)
.build()
#Bean
#Order(2)
fun actuatorSecurityConfiguration(
http: ServerHttpSecurity,
reactiveAuthenticationManager: ReactiveAuthenticationManager
): SecurityWebFilterChain = http
.securityMatcher(pathMatchers("/manage/**"))
.authenticationManager(reactiveAuthenticationManager)
.httpBasic { }
.build()
}
However, as those WebFilters are created as beans, they are registered automatically and are applied to all requests, seemingly outside the security chain.
For servlet filters, it is possible to disable this registration with a FilterRegistrationBean (see spring-boot documentation).
Is there a similar way for reactive WebFilters, or do I have to add additional URL filtering into those filters?
To find a solution, we first have to dig in a little deeper into how spring works and its internals.
All beans of type WebFilter are automatically added to the main web handling filter chain.
See spring boot documentation on the topic:
WebFilter beans found in the application context will be automatically used to filter each exchange.
So that happens even if you want them to be applied only to a specific spring-security filter chain.
(IMHO, it is a bit of a flaw of spring-security to re-use the Filter or WebFilter interfaces and not have something security-specific with the same signature.)
In code, the relevant part is in spring-web's WebHttpHandlerBuilder
public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
// ...
List<WebFilter> webFilters = context
.getBeanProvider(WebFilter.class)
.orderedStream()
.collect(Collectors.toList());
builder.filters(filters -> filters.addAll(webFilters));
// ...
}
Which in turn is called in a spring-boot's HttpHandlerAutoConfiguration to create the main HttpHandler.
#Bean
public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
// ...
return httpHandler;
}
To prevent those filters to be applied to all exchanges, it might be possible to simply not create them as beans and create them manually, as suggested in a comment above. Then the BeanProvider will not find them and not add them to the HttpHandler. However, you leave IoC-country and lose autoconfiguration for the filters. Not ideal when those filters become more complex or when you have a lot of them.
So instead my solution is to manually configure a HttpHandler for my application, which does not add my security-specific filters to the global filter chain.
To make this work, I first declare a marker interface for my filters.
interface NonGlobalFilter
class MySecurityFilter : WebFilter, NonGlobalFilter {
// ...
}
Then, a configuration class is required where the custom HttpHandler is created. Conveniently, WebHttpHandlerBuilder has a method to manipulate its filter list with a Consumer.
This will prevent spring-boot to use its own HttpHandler from HttpHandlerAutoConfiguration because it is annotated with #ConditionalOnMissingBean(HttpHandler.class).
#Configuration
class WebHttpHandlerConfiguration(
private val applicationContext: ApplicationContext
) {
#Bean
fun httpHandler() = WebHttpHandlerBuilder
.applicationContext(applicationContext)
.filters {
it.removeIf {
webFilter -> webFilter is NonGlobalFilter
}
}
.build()
}
And that's it! As always, spring provides a lot of useful defaults out of the box, but when it gets in your way, there will be a means to adjust it as necessary.

SpringBoot Spring Security can't have a given filter applied to only one path [duplicate]

I want to use #Autowire with a Filter. So I define my filter in the SecurityConfig as below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(getA(), BasicAuthenticationFilter.class);
http.csrf().disable();
}
#Bean
public A getA(){
return new A();
}
This filter A extends Spring's GenericFilterBean.
I get below output when I invoke the controller, which shows the filter hits twice.
filter A before
filter A before
mycontroller invoke
filter A after
filter A after
My observation is, this extra invocation invoke with Spring container because if filter is not register as bean, it only get hits once. What is the reason and how can I fix it?
As you have observed, Spring Boot will automatically register any bean that is a Filter with the servlet container. One option is to not expose your filter as a bean and only register it with Spring Security.
If you want to be able to autowire dependencies into your Filter then it needs to be a bean. That means you need to tell Spring Boot not to register it as a filter. As described in the documentation, you do that using a FilterRegistrationBean:
#Bean
public FilterRegistrationBean registration(MyFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
If you are on Spring 6.0.2 or plus version and if using OncePerRequestFilter,
Overriding shouldNotFilter method as follows will work.
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().contains("/api/path/which/needs/to/exclude");
}
Removing #Component from the filter class helped me.
public class AuthTokenFilter extends OncePerRequestFilter {
}

Forbid spring boot add a filter with #Bean to filterchain

I create a filter for shiro, use #Bean, but spring boot add it to servlet filterchain.
I used it in shiro filterChainDefinitions:
shiroFilterFactoryBean.getFilters().put("apiValid", getApiValidFilter());
#Bean(name = "apiValid")
public ApiValidFilter getApiValidFilter() {
return new ApiValidFilter();
}
I want the filter as a spring bean, to get benefit from spring, like use #Autowired, but do not want it add to filterchain automatic.
How to config spring boot to pervent this behavior?
As described in the documentation, you can create a FilterRegistrationBean for the Filter and mark it as disabled:
#Bean
public FilterRegistrationBean registration(MyFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}

Resources