I have two security configurations in two libs
First one is for authentication:
@Bean
@Order(10)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers(createAntRequestMatchers(whitelist))
.permitAll().anyRequest()
.authenticated()
)
.oauth2ResourceServer( ...)
return http.build();
}
Second one adds some resource filter:
@Bean
@Order(100)
public SecurityFilterChain filterChain(HttpSecurity http, ResourceFilter resourceFilter) throws Exception {
return http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers(createAntRequestMatchers(whitelist))
.permitAll().anyRequest()
.authenticated()
).addFilterAfter(resourceFilter, SessionManagementFilter.class).build();
}
It worked perfect until spring-boot 3.3.? After update to spring-boot 3.4.1 spring context don't startet anymore with error message
A filter chain that matches any request [DefaultSecurityFilterChain defined as 'filterChain' in ... has already been configured, which means that this filter chain ... 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.
After I add in each configuration requestMatcher (all requests)
http.securityMatcher("/**").authorizeHttpRequests(...
it works as expected. But if I read spring-security issue comments https://github.com/spring-projects/spring-security/issues/15220 I have a doubts about my solution.
What do you mean?
I adapt my code acording @Roar S. suggestion
@Bean
@Order(10)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/**")
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers(createAntRequestMatchers(whitelist))
.permitAll().anyRequest()
.authenticated()
)
.oauth2ResourceServer( ...)
return http.build();
}
---------
@Bean
@Order(100)
public SecurityFilterChain filterChain(HttpSecurity http, ResourceFilter resourceFilter) throws Exception {
return http.securityMatcher("/**")
.addFilterAfter(resourceFilter, SessionManagementFilter.class).build();
}
It works, but .securityMatcher("/**")
looks suspicious. And without .securityMatcher("/**")
it doesn't start
Update: OP mentioned in a comment that the first SecurityFilterChain
is shared across multiple applications and cannot be modified. Since the issue involves simply adding a filter that needs to execute after the shared SecurityFilterChain
, we can address it using FilterRegistrationBean
instead of using two security chains. The following code is based on this answer.
LoggingFilter
is the same as in my original answer.
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<LoggingFilter> afterAuthFilterRegistrationBean(
SecurityProperties securityProperties) {
var filterRegistrationBean = new FilterRegistrationBean<LoggingFilter>();
// a filter that extends OncePerRequestFilter
filterRegistrationBean.setFilter(new LoggingFilter());
// this needs to be a number greater than than spring.security.filter.order
filterRegistrationBean.setOrder(securityProperties.getFilter().getOrder() + 1);
return filterRegistrationBean;
}
}
Original answer
OP has separated security configuration into two chains under the assumption, I believe, that the principal becomes available only after a security chain is fully executed. However, the principal is populated and available after the BearerTokenAuthenticationFilter
has completed. Therefore, the two chains in the question can be merged into one.
This behavior can be verified by adding the following logging filter to the chain with:
.addFilterAfter(new LoggingFilter(), BearerTokenAuthenticationFilter.class)
Here is the logging filter implementation:
private static class LoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
var authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
LOG.info("Logged in as: {}", authentication.getName());
LOG.info("Authorities: {}",
authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(", "))
);
} else {
LOG.info("No user");
}
filterChain.doFilter(request, response);
}
}