Spring Security OAuth2: '#oauth2.xxx' expressions not evaluted with multiple RequestMatchers mapped to the same FilterChain - spring

In a multi modules app I've defined 5 RequestMatchers mapped to the same FilterChain, like below:
#Configuration
public class Module1SecurityFilterChain extends ResourceServerConfigurerAdapter {
#Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module1/**")
.and()
.authorizeRequests()
.antMatchers( "/module1/resource").authenticated()
.antMatchers( "/module1/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
And module2:
#Configuration
public class Module2SecurityFilterChain extends ResourceServerConfigurerAdapter {
#Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module2/**")
.and()
.authorizeRequests()
.antMatchers( "/module2/resource").authenticated()
.antMatchers( "/module2/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
And enabled method security:
#Configuration
#EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
The problem is that all #oauth2.xx expressions are evaluated only for the 1st module requestmatcher /module1/** and ignored in others. When I authenticate a user and try to access to /module1/test the access is denied as expected whereas when accessing to /module2/test access is granted (it should also be denied).
Could someone explains me why and how to solve this? I know Spring Security isn't easy at all...
Thanks again.
EDIT
#Darren Forsythe (thanks for your comment)
The filter chains created are:
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#f55a810b, org.springframework.security.web.context.SecurityContextPersistenceFilter#85021903, org.springframework.security.web.header.HeaderWriterFilter#1d0744d1, org.springframework.security.web.authentication.logout.LogoutFilter#2d15146a, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#c38f3266, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#8f9bf85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#74a71be5, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#e4eb6cc, org.springframework.security.web.session.SessionManagementFilter#22f6b39a, org.springframework.security.web.access.ExceptionTranslationFilter#960c464f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#f7a19dc5]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/module1/**'], Ant [pattern='/module2/**'], Ant [pattern='/module3/**'], Ant [pattern='/module4/**'], Ant [pattern='/module5/**'], Ant [pattern='/module6/**']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#38ef2427, org.springframework.security.web.context.SecurityContextPersistenceFilter#a26ff7af, org.springframework.security.web.header.HeaderWriterFilter#5344e710, org.springframework.security.web.authentication.logout.LogoutFilter#da0534c8, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#2956c7ab, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#5682f610, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#f4cbf7a4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#d1b1395a, org.springframework.security.web.session.SessionManagementFilter#d352f8ab, org.springframework.security.web.access.ExceptionTranslationFilter#9bb1d86, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#73c7a695]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#1cc2056f, org.springframework.security.web.context.SecurityContextPersistenceFilter#259d95db, org.springframework.security.web.header.HeaderWriterFilter#de089e0b, org.springframework.security.web.authentication.logout.LogoutFilter#8b86b4c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#96304ca8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#1d5b7e4b, org.springframework.security.web.session.SessionManagementFilter#bd586b4d, org.springframework.security.web.access.ExceptionTranslationFilter#7cff2571]
As you can see, all module's urls are mapped to the same filter chain with this list of filters:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
What I don't understand is why for the other modules the #oauth2.xx expression is not evaluated since the FilterChain is the same?

Request matchers (specified in antMatchers, anyRequest, etc.) are processed by the filter chain in the order they are specified. Because multiple ResourceServiceConfiguredAdapter instances simply configure off of the same instance of HttpSecurity, the matchers are processed something like this for each one of your requests:
if (uri == "/module1/resource") {
// ...
} else if (uri == "/module1/test") {
// ...
} else if (true) { // anyRequest
// ...
} else if (uri = "/module2/resource") {
// ...
} else if (uri = "/module2/test") {
// ...
}
As you can see, the last two if conditions would never get hit.
Here are two things you can consider:
Replace anyRequest()
anyRequest is usually very handy; however, in this case, you don't actually mean "any request" since you are trying to narrow the scope to certain module paths. You might instead do:
http
.requestMatchers()
.antMatchers("/module2/**")
.and()
.authorizeRequests()
.antMatchers("/module2/resource").authenticated()
.antMatchers("/module2/test").access( "#oauth2.isClient()")
.antMatchers("/module2/**").access( "#oauth2.hasScope('webclient')" );
That way, the module doesn't overreach and try and specify behavior that maybe it doesn't know about.
Truthfully, it is typically harmless to call anyRequest since you are already narrowing the scope of the filter chain already with requestMatchers. But, because you are composing a single HttpSecurity with multiple adapters, there is this hidden complexity.
oauth2ResourceServer() - Spring Security 5.1+
If you are on Spring Security 5.1, then there is actually support built into WebSecurityConfigurerAdapter natively, so you don't need to use ResourceServerConfigurerAdapter anymore, at least for JWT-encoded tokens at this point. This is also nice because two WebSecurityConfigurerAdapters are treated as separate filter chains.
Depending on the OAuth 2.0 features you need, you may be able to do that instead:
#EnableWebSecurity
public class Module1Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/module1/**")
.and()
.authorizeRequests()
.antMatchers("/module1/resource").authenticated()
.anyRequest.hasRole("SCOPE_webclient")
.oauth2ResourceServer()
.jwt();
}
}
This is an active area of development for Spring Security right now - porting features over into WebSecurityConfigurerAdapter; so definitely reach out with your specific use case to make sure that it gets prioritized if it isn't already in place.

Related

Spring Security 6 CustomAuthenticationFilter(intend to replace the UsernamePasswordAuthenticationFilter) does not work

By reference https://www.baeldung.com/spring-security-extra-login-fields
I intend to customize the the functionality of Spring security Authentication UsernamePasswordAuthenticationFilter to get the additional customized "loginForm" fields.
I created one customized filter
CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter
and one customized authentication token
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken
By using the below configuration:
#EnableWebSecurity
#Configuration(proxyBeanMethods = false)
public class AuthenticationSecurityConfig extends AbstractHttpConfigurer<AuthenticationSecurityConfig, HttpSecurity> {
#Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http.addFilterBefore(authenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
}
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.requestMatchers("/css/**", "/login")
.permitAll()
.requestMatchers("/resources/**")
.permitAll()
.and()
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout()
.logoutUrl("/logout")
.and()
.authorizeHttpRequests(
auth -> auth.anyRequest().authenticated()
)
.apply(securityConfig());
return http.build();
}
}
I can successfully make the my AuthenticationProvider's
authenticate
method to be executed, and It can successfully return one "authenticated"
Authentication object.
However it will not redirect to the OAuth2 client's redirect page(I setup my project as one OIDC authorization server), it will stay at login page, the log will be like below:
.HttpSessionRequestCache : Saved request http://192.168.0.107:9000/oauth2/authorize?client_id=messaging-client&redirect_uri=http%3A%2F%2F192.168.0.107%3A8080%2Fauth%2Fsigninwin%2Fmain&response_type=code&scope=openid%20profile%20message.read&state=802af0dcd82a483eb726c1dffff0867d&code_challenge=t_tfBjZPRd228uEZuQJ56clfXokGYqiwkudQqKhWQqo&code_challenge_method=S256&prompt=login&response_mode=query&continue&continue to session
2022-11-30T19:48:05.063+08:00 DEBUG 63801 --- [nio-9000-exec-5] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://192.168.0.107:9000/login
2022-11-30T19:48:05.078+08:00 DEBUG 63801 --- [nio-9000-exec-6] o.s.security.web.FilterChainProxy : Securing GET /login
2022-11-30T19:48:05.078+08:00 DEBUG 63801 --- [nio-9000-exec-6] o.s.security.web.FilterChainProxy : Secured GET /login
2022-11-30T19:48:05.084+08:00 DEBUG 63801 --- [nio-9000-exec-6] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
If I will not use the customized filter CustomAuthenticationFilter, every thing will be fine(But of couse I can't get the additonal LoginForm fileds).
My build.gradle
plugins {
id "org.springframework.boot" version "3.0.0-RC2"
id "io.spring.dependency-management" version "1.0.11.RELEASE"
id "java"
}
implementation "org.springframework.security:spring-security-oauth2-authorization-server:1.0.0"
Any ideas?
I suspected that the attributes of CustomAuthenticationFilter(use new to create the object) is not the same with attributes of the UsernamePasswordAuthenticationFilter that spring security framework will initialize, need further check the disparity.
There are two solutions,
the difference with previous versions is due to this change: https://github.com/spring-projects/spring-security/issues/11110
*** First solution ***
You can use the old strategy (deprecated) with automatic saving of the SecurityContextHolder setting:
http.securityContext((securityContext) -> securityContext.requireExplicitSave(false))
*** Second solution ***
Define an HttpSessionSecurityContextRepository:
#Bean
public HttpSessionSecurityContextRepository securityContextRepository() {
return new HttpSessionSecurityContextRepository();
}
Set to http configuration:
http.securityContext().securityContextRepository(securityContextRepository())
finally set the context repository to your custom filter:
filter.setSecurityContextRepository(securityContextRepository());

what's the proper way to add Spring Security filter only for specific URL? [duplicate]

This question already has answers here:
Filter invoke twice when register as Spring bean
(3 answers)
Closed 5 months ago.
I am trying to add custom filter to only specific URL, however the filter get applied to every request, regardless of URL and method, does anybody know the proper way to fix this using latest from Spring Security, i.e. not using WebSecurityConfigurerAdapter, because it is going to be deprecated.
There are many similar questions here, but they either do not work for me, or they use the "old" approach (like this and this and many other), or both.
I have number of endpoints exposed that all follow the pattern: /api/** however I need to provide some authentication for a specific endpoint: /api/some/url and a particular method (GET in this case), how do I do this properly?
NOTE: the endpoint URLs are all under /api/* (should they be called nested?)
My security configuration looks like this:
#EnableWebSecurity
public class SecurityConfig {
private MyFilter myFilter;
public SecurityConfig(MyFilter pif) {
myFilter = pif;
}
/**
* Handling AuthZ & AuthN for most APIs. No AuthZ & AuthN.
*/
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain defaultSecurity(HttpSecurity http) throws Exception {
http.requestMatchers((requests) ->
requests.antMatchers("/"))
.authorizeHttpRequests((authorize) -> authorize.anyRequest()
.permitAll());
return http.build();
}
/**
* Handling AuthZ & AuthN for GET /api/some/url.
*/
#Bean
public SecurityFilterChain keyApiSecurity(HttpSecurity http) throws Exception {
http.requestMatchers((requests) -> requests
.antMatchers(HttpMethod.GET, "/api/some/url").and())
.addFilterBefore(myFilter,
BasicAuthenticationFilter.class)
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll());
return http.build();
}
}
When you expose some GenericFilter implementation as a bean in spring-boot, it automatically puts it in a common filter chain for any request, because it doesn't know if it's a security filter or not - it could be a logging filter or anything else.
So this filter bean will be executed regardless of spring-security.
Your defaultSecurity filter chain doesn't have this custom filter, so MyFilter will be executed after spring security filter chain due to the order.
At the same time, keyApiSecurity filter chain sets this custom filter before BasicAuthenticationFilter, so it will be executed there, and will not be executed the second time, because basic doFilter() implementation of OncePerRequestFilters method checks whether the request was already filtered by the filter.
So, if you want your filter to work only as a security filter, you should not expose it as a bean, and you should set it in a security filter chain like this:
.addFilterBefore(new MyFilter(), BasicAuthenticationFilter.class)
Also you should think about setting the lowest priority for a "default" security filter chain, because if it's selected first - other security filter chains will be totally ignored. So I think some specific filter security chains should have higher priority.
EDIT:
If you can't set your security filter with the new operator because you rely on bean injection in this Filter implementation, you can override shouldNotFilter(HttpServletRequest request) method of OncePerRequestFilter, for example like this:
#Component
public class MyFilter extends OncePerRequestFilter {
private final RequestMatcher uriMatcher =
new AntPathRequestMatcher("/api/some/url", HttpMethod.GET.name());
// some bean injection...
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// filter logic here...
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
RequestMatcher matcher = new NegatedRequestMatcher(uriMatcher);
return matcher.matches(request);
}
}
Then it will filter only matched requests, and putting it into the security filter chain will set its order.

Spring security - create 2 filter chains with specific matchers

I'm in the process of implementing ADFS support to an existing spring project.
Since we already have our own JWT authentication, which we want to work in parallel to ADFS authentication, I want to implement a new filter chain that will handle only certain API request paths.
By this I mean I want to create:
ADFS filter chain that will handle all the /adfs/saml/** API calls
Leave the default filter chain that will handle all the rest API calls
I'm using the ADFS spring security lib that defines the filter chain like this:
public abstract class SAMLWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//some code
protected final HttpSecurity samlizedConfig(final HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(samlEntryPoint())
.and()
.csrf().ignoringAntMatchers("/saml/**")
.and()
.authorizeRequests().antMatchers("/saml/**").permitAll()
.and()
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(filterChainProxy(), BasicAuthenticationFilter.class);
// store CSRF token in cookie
if (samlConfigBean().getStoreCsrfTokenInCookie()) {
http.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
return http;
}
}
And I extend this class:
#EnableWebSecurity
#Configuration
#Order(15)
#RequiredArgsConstructor
public class ADFSSecurityConfiguration extends SAMLWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.authorizeRequests()
.antMatchers("/adfs")
.authenticated();
}
}
But when debugging I see that this new filter chain is set to match "any" request.
So I'm probably setting the matchers wrong.
Actually, after reading the official docs the answer was a simple one:
(see "Creating and Customizing Filter Chains" section)
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.antMatcher("/adfs/**");
}
It should not be put after .authorizeRequests() but strait on the first matcher.

Securing same endpoint with multiple configurations

We have a microservice architecture with securities for front to back with JWT, and back-to-back security with HTTP Basic.
Here is our configuration class for JWT :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers(endpointsProperties.getJwtWithWildcard())
.and()
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
jwtFilter is a simple filter that reads the Authorization header, and set the SecurityContextHolder.
And the HTTP Basic :
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
for (Map<String, String> userData : properties.getUsers()) {
auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser(userData.get("login")).password(userData.get("password")).authorities(BASIC_AUTH_AUTHORITY);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers(endpoints.getBasicWithWildcard() )
.and().csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic();
}
Those configuration class are used in differnets services, with distinct JWT and HTTP Auth endpoints. They are used either at the same time or independently. EndpointsProperties are loaded from application.yml.
But now, we have some referential microservices that we want to be reached either by other services or direclty by a (web) frontend application. We want to know if it is possible to secure a same URL ('/api/referential', for example) with the two different methods. Combining those configuration class with the same endpoints does not work, and it seems one configuration eraze the other.
Is it possible to secure a same URL path with different methods like that ? If yes, what do we need to change to enable it ?
Thanks for your support.
I think you can just add the two filters to the filter chain in this order
BasicAuthenticationFilter
JwtFilter
and make sure the ignoreFailure property of the BasicAuthenticationFilter is set to true.
This will make The basicAuthFilter authenticate requests with basicAuth and just continue down the filter chain if no basicAuth is sent - thus delegating to the JwtFilter.
then remove .httpBasic() from the WebsecurityConfig - as it will try to add another BasicSecurityFilter.
Just an off the cuff idea

Spring Boot Security PreAuthenticated Scenario with Anonymous access

I have a Spring Boot (1.5.6) application that is using the "pre-authenticated" authentication scenario (SiteMinder) from Spring Security.
I have a need to expose the actuator "health" endpoint anonymously meaning the requests to that endpoint will not go through SiteMinder and as a result, the SM_USER header will not be present in the HTTP Request Header.
The problem I'm facing is that no matter how I try to configure the "health" endpoint, the framework is throwing an org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException because the expected header ("SM_USER") is not present when the request does not go through SiteMinder.
This was my original security config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().antMatchers("/cars/**", "/dealers/**")
.hasAnyRole("CLIENT", "ADMIN")
.and()
.authorizeRequests().antMatchers("/health")
.permitAll()
.and()
.authorizeRequests().anyRequest().denyAll()
.and()
.addFilter(requestHeaderAuthenticationFilter())
.csrf().disable();
}
#Bean
public Filter requestHeaderAuthenticationFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthProvider());
}
#Bean
public AuthenticationProvider preAuthProvider() {
PreAuthenticatedAuthenticationProvider authManager = new PreAuthenticatedAuthenticationProvider();
authManager.setPreAuthenticatedUserDetailsService(preAuthUserDetailsService());
return authManager;
}
#Bean
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> preAuthUserDetailsService() {
return new UserDetailsByNameServiceWrapper<>(inMemoryUserDetails());
}
#Bean
public UserDetailsService inMemoryUserDetails() {
return new InMemoryUserDetailsManager(getUserSource().getUsers());
}
#Bean
public UserHolder getUserHolder() {
return new UserHolderSpringSecurityImple();
}
#Bean
#ConfigurationProperties
public UserSource getUserSource() {
return new UserSource();
}
I've tried to exclude the /health endpoint a couple different ways to no avail.
Things I've tried:
Configure health endpoint for anonymous access rather than permitAll:
http
.authorizeRequests().antMatchers("/health")
.anonymous()
Configure WebSecurity to ignore the health endpoint:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
}
Turn off security for all actuator endpoints (not idea but I was grasping for straws):
management.security.enabled=false
Looking at the logs, the problem seems to be that the RequestHeaderAuthenticationFilter is getting registered as a top level filter rather than a filter in the existing securityFilterChain:
.s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webRequestLoggingFilter' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestHeaderAuthenticationFilter' to: [/*]
Based on my understanding, because the RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter, the framework knows where to insert the filter within the chain which is why I'm not tinkering with the addFilterBefore or addFilterAfter variants. Maybe I should be? Does anybody know the correct place to insert the filter explicitly? (I thought the need for explicitly specifying filter order was removed in prior versions of Spring Security)
I know I can configure the RequestHeaderAuthenticationFilter so that it doesn't throw an exception if the header is not present but I'd like to keep that on if at all possible.
I found this SO post that seems to be similar to my problem but unfortunately there's no answer there either.
spring-boot-security-preauthentication-with-permitted-resources-still-authenti
Any help would be greatly appreciated!
Thanks!
The problem was indeed the fact that the RequestHeaderAuthenticationFilter was being registered both as a top level filter (unwanted) and also within the Spring Security FilterChain (desired).
The reason for the "double registration" is because Spring Boot will register any Filter Beans with the Servlet Container automatically.
In order to prevent the "auto-registration" I just had to define a FilterRegistrationBean like so:
#Bean
public FilterRegistrationBean registration(RequestHeaderAuthenticationFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Docs:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-disable-registration-of-a-servlet-or-filter
An alternate/simpler solution:
Just don't mark the RequestHeaderAuthenticationFilter class as an #Bean which is fine because there's no kind of DI needed for that particular filter. By not marking the filter with #Bean, Boot won't try to auto register it which removes the need to define the FilterRegistrationBean.
The provided answer is not complete most likely because Sean Casey made so many trial and error changes that lost track of which configuration actually fixed the problem. I am posting my findings which I believe have the correct configuration:
If you have incorrectly registered the RequestHeaderAuthenticationFilter as a #Bean, as the original answer says, then remove it. Just creating it as a normal instance and adding it as a filter registers it properly:
#Override
protected void configure(HttpSecurity http) throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
// configure your filter
http
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilter(filter)
.csrf().disable();
}
The magical configuration which Sean Casey tried but initially failed (due to the double registering of the auth filter) is the WebSecurity configuration:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
}
Notice that adding the HttpSecurity antMatchers does nothing in that case, it seems that WebSecurity is the one taking precedence and controlling what goes through.
EXTRA: Per Spring Security documentation, the WebSecurity.ignore method should be used for static resources, not for dynamic ones which should instead be mapped to allow all users. However in this case it seems the mapping gets overridden by the PreAuthentication filter which forces the use of the more aggressive ignore scenario.

Resources