I have two types of filter: a public one that is used to filter public endpoints with a basic JWT authentication and a private one that is used to filter private endpoints (endpoints that are only accessible from other micro services but not public, JWT authentication too but internal).
When I try to access the following endpoint “/api/users/dashboard”, I got an error because the PrivateAuthenticationFilter is invoked even though it should not be and I don’t understand why.
I have probably misunderstood how Spring Security filters work. Can you give me a solution?
SecurityConfiguration:
@Bean
@Order(1)
public SecurityFilterChain privateSecurityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/private/**")
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> {
auth.anyRequest().authenticated();
})
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(privateAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain publicSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/api/users/register").permitAll();
auth.anyRequest().authenticated();
})
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(publicAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
PrivateAuthenticationFilter:
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
try {
final String jwt = authHeader.substring(7);
if (jwtService.isPrivateTokenValid(jwt)) {
Authentication authToken = jwtService.getPrivateAuthentication(jwt);
if (authToken == null) {
throw new BadCredentialsException("Invalid JWT token");
}
SecurityContextHolder.getContext().setAuthentication(authToken);
} else {
throw new BadCredentialsException("Invalid JWT token");
}
} catch (Exception e) {
log.error("An error occurred while public filtering: {}", e.getMessage());
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return;
}
filterChain.doFilter(request, response);
}
PublicAuthenticationFilter:
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
try {
final String jwt = authHeader.substring(7);
final String userEmail = jwtService.extractUsername(jwt);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (userEmail != null && authentication == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isPublicTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
} catch (JwtException e) {
log.error("JwtException: {}", e.getMessage());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
} catch (UsernameNotFoundException e) {
log.error("UsernameNotFoundException: {}", e.getMessage());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
} catch (Exception e) {
log.error("An error occurred while public filtering", e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}