I am trying to combine "normal" form login for users in the database with a simple API key authentication for paths under /api/**.
When I access a URL under /api/..., this seems to work as expected, as in without the correct API key, I get a 401 and with it, I get a 400.
Problem is, the configuration from the Order(1) chain seems to be applied to routes that don't start with /api/ despite the securityMatcher directive proceeding it. Meaning that if I access http://localhost:8080 in a browser, expecting to see the login form I saw before adding the ApiKey authentication, I also get a 401 in the Format defined in apiKeyAuthfilter/unauthorizedHandler.
If I remove everything I added to try and add the ApiKey authentication, the formLogin works just fine.
It seems like the ApiKeyAuthFilter is applied even for the Order(2) chain? How can I prevent that?
So far, I have the following configuration:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class MyLinksConfiguration {
private final ApiKeyAuthFilter apiKeyAuthFilter;
private final UnauthorizedHandler unauthorizedHandler;
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return new DbUserDetailsService(userRepository);
}
@Bean
public AuthenticationManager authenticationManager(
PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
var provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userDetailsService);
return new ProviderManager(provider);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
return http.securityMatcher("/api/**")
.addFilterBefore(apiKeyAuthFilter, ChannelProcessingFilter.class)
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(unauthorizedHandler))
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
// .formLogin(AbstractHttpConfigurer::disable)
// .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// .sessionManagement(AbstractHttpConfigurer::disable)
// .addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
// .csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
requests -> {
requests.anyRequest().authenticated();
})
.formLogin(
form -> {
form.loginPage("/login").permitAll();
})
.logout(LogoutConfigurer::permitAll)
.build();
}
}
With commented out parts showing some of the things I tried.
ApiKeyAuthFilter is
@Component
public class ApiKeyAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
var request = (HttpServletRequest) servletRequest;
var response = (HttpServletResponse) servletResponse;
var apiKey = request.getHeader("ApiKey");
if (apiKey == null) {
unauthorized(response);
return;
}
if (!apiKey.equals("sillytest")) {
unauthorized(response);
return;
}
filterChain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void destroy() {
Filter.super.destroy();
}
private void unauthorized(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(401);
Map<String, Object> response = Map.of("message", "SC_UNAUTHORIZED");
String responseBody = new ObjectMapper().writeValueAsString(response);
httpServletResponse.getWriter().write(responseBody);
}
}
and UnauthorizedHandler is
@Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.setStatus(401);
Map<String, Object> r = Map.of("message", "SC_UNAUTHORIZED");
String responseBody = new ObjectMapper().writeValueAsString(r);
response.getWriter().write(responseBody);
}
}
DbUserDetailsService (which is only supposed to be used for the Order(2) chain is:
@RequiredArgsConstructor
public class DbUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.findByUsername(username);
return user.map(DbUserDetails::new).orElseThrow(() -> new UsernameNotFoundException(username));
}
}
while the related DbUserDetails is:
@RequiredArgsConstructor
public class DbUserDetails implements UserDetails {
private final User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (user.isAdmin()) {
return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
return List.of();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return user.isActive();
}
@Override
public boolean isAccountNonLocked() {
return user.isActive();
}
@Override
public boolean isCredentialsNonExpired() {
return user.isActive();
}
@Override
public boolean isEnabled() {
return user.isActive();
}
}
With logging enabled as suggested by the first comment, I get the following which I don't quite understand yet. The traceback in there doesn't have any of my code in it:
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain defined as 'apiFilterChain' in [class path resource [de/afoo/mylinks/MyLinksConfiguration.class]] matching [Or [Mvc [pattern='/api/**']]] and having filters [DisableEncodeUrl, ApiKeyAuth, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Logout, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation] (1/2)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain defined as 'filterChain' in [class path resource [de/afoo/mylinks/MyLinksConfiguration.class]] matching [any request] and having filters [DisableEncodeUrl, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Logout, UsernamePasswordAuthentication, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation, Authorization] (2/2)
2025-02-03T21:44:40.538+01:00 DEBUG 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Securing GET /login
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking LogoutFilter (5/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.s.w.a.logout.LogoutFilter : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking UsernamePasswordAuthenticationFilter (6/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking RequestCacheAwareFilter (7/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.s.w.s.HttpSessionRequestCache : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderAwareRequestFilter (8/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking AnonymousAuthenticationFilter (9/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (10/11)
2025-02-03T21:44:40.538+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking AuthorizationFilter (11/11)
2025-02-03T21:44:40.540+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] estMatcherDelegatingAuthorizationManager : Authorizing GET /login
2025-02-03T21:44:40.540+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] estMatcherDelegatingAuthorizationManager : Checking authorization on GET /login using org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer$$Lambda/0x00000191de9deda8@11f38aa2
2025-02-03T21:44:40.540+01:00 DEBUG 34228 --- [mylinks] [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Secured GET /login
2025-02-03T21:44:40.540+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-5] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain defined as 'apiFilterChain' in [class path resource [de/afoo/mylinks/MyLinksConfiguration.class]] matching [Or [Mvc [pattern='/api/**']]] and having filters [DisableEncodeUrl, ApiKeyAuth, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Logout, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation] (1/2)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain defined as 'filterChain' in [class path resource [de/afoo/mylinks/MyLinksConfiguration.class]] matching [any request] and having filters [DisableEncodeUrl, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Logout, UsernamePasswordAuthentication, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation, Authorization] (2/2)
2025-02-03T21:44:40.556+01:00 DEBUG 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Securing GET /favicon.ico
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking LogoutFilter (5/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.w.a.logout.LogoutFilter : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking UsernamePasswordAuthenticationFilter (6/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking RequestCacheAwareFilter (7/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.w.s.HttpSessionRequestCache : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderAwareRequestFilter (8/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking AnonymousAuthenticationFilter (9/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (10/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.security.web.FilterChainProxy : Invoking AuthorizationFilter (11/11)
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] estMatcherDelegatingAuthorizationManager : Authorizing GET /favicon.ico
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] estMatcherDelegatingAuthorizationManager : Checking authorization on GET /favicon.ico using org.springframework.security.authorization.AuthenticatedAuthorizationManager@6957a2e2
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
2025-02-03T21:44:40.556+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied
org.springframework.security.authorization.AuthorizationDeniedException: Access Denied
at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:99) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.4.2.jar:6.4.2]
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:243) ~[spring-webmvc-6.2.2.jar:6.2.2]
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:238) ~[spring-security-config-6.4.2.jar:6.4.2]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) ~[spring-web-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.2.2.jar:6.2.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
2025-02-03T21:44:40.557+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.w.s.HttpSessionRequestCache : Did not save request since it did not match [And [Not [Ant [pattern='/**/favicon.*']], Not [MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@7c9c7e7d, matchingMediaTypes=[application/json], useEquals=false, ignoredMediaTypes=[*/*]]], Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], Not [MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@7c9c7e7d, matchingMediaTypes=[multipart/form-data], useEquals=false, ignoredMediaTypes=[*/*]]], Not [MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@7c9c7e7d, matchingMediaTypes=[text/event-stream], useEquals=false, ignoredMediaTypes=[*/*]]]]]
2025-02-03T21:44:40.557+01:00 DEBUG 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://localhost:8080/login
2025-02-03T21:44:40.557+01:00 TRACE 34228 --- [mylinks] [nio-8080-exec-6] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
Based on your logs, /login
and /favicon.ico
were both handed by the second filter chain, as expected.
What I believe is happening is that since ApiKeyAuthFilter
is a @Component
, it is getting picked up by Spring Boot as well, causing it to be invoked on every request.
The Spring Security reference reviews some alternatives including the following:
@Bean
public FilterRegistrationBean<ApiKeyAuthFilter> tenantFilterRegistration(ApiKeyAuthFilter filter) {
FilterRegistrationBean<ApiKeyAuthFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
If you are interested, you can follow an issue that is currently being actively discussed to find a simpler solution: https://github.com/spring-projects/spring-security/issues/16222