Short answer to your question:
@Configuration
@ComponentScan
@ConfigurationPropertiesScan
class CommonConfig {
@Autowired
lateinit var chains: List<SecurityFilterChain>
@Autowired
lateinit var myCustomFilter: MyCustomFilter
@PostConstruct
fun post() {
chains.forEach {
for (i in it.filters.indices){
if (it.filters[i] is LogoutFilter) {
it.filters.add(i + 1, myCustomFilter)
}
}
}
}
}
More details below:
Let's say we have the following chain:
@Bean
@Throws(Exception::class)
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/first", "/second")
.authenticated()
.anyRequest()
.permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(auditAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().disable()
.csrf().disable()
.cors().configurationSource(corsConfigurationSource())
return http.build()
}
And we want to add the following filter to this chain between some specific filters:
@Component
class MyCustomFilter : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
//some work: adding headers, working with authentication or something else
filterChain.doFilter(request, response)
}
}
Here are a few ways to add our filter to the chain:
1) adding the @Order annotation to the filter:
@Component
@Order(5)
class MyCustomFilter : OncePerRequestFilter()
Note that:
- If you do not specify an order in the annotation, then the default order is:
Ordered.LOWEST_PRECEDENCE
- your filter will be the last one in the chain.
- If you are working with Authentication in your filter (through authentication providers) then
Ordered.LOWEST_PRECEDENCE
and Ordered.HIGHEST_PRECEDENCE
not suitable for your case because:
- in case
Ordered.HIGHEST_PRECEDENCE
filterSecurityContextPersistenceFilter
which will be executed after your
filter will clear the SecurityContext even if your filter added an Authentication object to the SecurityContext
- in the case of
Ordered.LOWEST_PRECEDENCE
your filter will not be called at all: AnonymousAuthenticationFilter will set the Authentication object to
Anonymous and you will receive a 401 or 403 response before the chain calls your filter
- In this case, you need to get the chain and determine the specific order of your filter
via the
FilterChainProxy
and the doFilter(ServletRequest request, ServletResponse response)
method, getting this.additionalFilters
. For example, 5
.
But this is not the best way to determine the order
2) The standard (and best) way is through setting up the filter chain itself:
@Bean
@Throws(Exception::class)
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeRequests()
...
.addFilterBefore(MyCustomFulter(), BasicAuthenticationFilter::class.java)
...
return http.build()
}
3) Injecting a filter into a chain in @PostConstruct
In my case, .addFilterBefore() was not possible due to the fact that
the filter was added from the starter. My starter needs to add a filter
to each chain after the LogoutFilter. Since there can be multiple chains in
a context, I add a filter to each one. Thus, when adding my starter to the application
, the filter is added to the required place without any additional configuration:
@Configuration
@ComponentScan
@ConfigurationPropertiesScan
class CommonConfig {
@Autowired
lateinit var chains: List<SecurityFilterChain>
@Autowired
lateinit var myCustomFilter: MyCustomFilter
@PostConstruct
fun post() {
chains.forEach {
for (i in it.filters.indices){
if (it.filters[i] is LogoutFilter) {
it.filters.add(i + 1, myCustomFilter)
}
}
}
}
}