1

I have an issue in my Spring Boot 3.0.2 project that uses Spring Security 6.0.2. I have created the following JwtAuthenticationFilter class for authorizing JWTs:

@Log4j2
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;
    private final ObjectMapper objectMapper;

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws IOException {
        try {
            final String authHeader = request.getHeader("Authorization");
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                String errorPayload = objectMapper.writeValueAsString(Map.of(
                        "type", "about:blank",
                        "title", "Bad Authorization",
                        "status", HttpServletResponse.SC_UNAUTHORIZED,
                        "detail", "The Authorization header should contain a Bearer token value."
                ));
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.setContentType("application/problem+json");
                response.getWriter().write(errorPayload);
                return;
            }

            final String jwt = authHeader.substring(7);
            final String userEmail = jwtService.extractUsername(jwt);
            if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
                if (jwtService.isTokenValid(jwt, userDetails)) {
                    var authToken = new UsernamePasswordAuthenticationToken(
                            userDetails,
                            null,
                            userDetails.getAuthorities()
                    );
                    authToken.setDetails(
                            new WebAuthenticationDetailsSource().buildDetails(request)
                    );
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }

            filterChain.doFilter(request, response);
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            String errorPayload = objectMapper.writeValueAsString(Map.of(
                    "type", "about:blank",
                    "title", "SF1",
                    "status", HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "detail", "An error occurred while processing the request."
            ));
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.setContentType("application/problem+json");
            response.getWriter().write(errorPayload);
        }
    }
}

This is my Spring Security configuration class:

@Configuration
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(11);
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(Customizer.withDefaults())
                .csrf(CsrfConfigurer::disable)
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/api/auth/**")
                            .permitAll()
                        .requestMatchers(HttpMethod.OPTIONS, "/**")
                            .permitAll()
                        .anyRequest()
                            .authenticated()
                )
                .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public CorsFilter corsFilter() {
        var source = new UrlBasedCorsConfigurationSource();
        var config = new CorsConfiguration();
        config.applyPermitDefaultValues();
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
        config.setAllowedOrigins(List.of("http://localhost:4200"));
        config.setAllowCredentials(true);
        config.addAllowedHeader("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

When I comment out the line .addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class), the authorization rules work correctly and I can obtain the JWT. However when I add this filter, it gets triggered for every request and I can no longer call the endpoint for obtaining the JWT, because its protected.

bezbos.
  • 1,551
  • 2
  • 18
  • 33
  • 2
    When Spring Boot detects a filter it will be automatically added to the filter chain, you are adding it again to the security filter chain. You need an additional `FilterRegistationBean` to prevent the registration in the regular filter chain. – M. Deinum Feb 22 '23 at 09:13
  • 3
    is there any specific reason you are not using the built in JWT handling that already comes with spring security? https://github.com/Tandolf/spring-security-jwt-demo – Toerktumlare Feb 22 '23 at 12:44
  • I wasn't aware they had a built-in mechanism for it. Can you link it to me? – bezbos. Feb 22 '23 at 12:47

1 Answers1

2

In my case I solved it by overriding the shouldNotFilter method of the OncePerRequestFilter.

I have a configuration with some not-to-be-filtered URIs, which I then match to the HttpServletRequest's request URI.

I'm sure there are better solutions and this is more of a 'cheap' hack, but it may be what you're looking for in case you want an easy solution.

Cheers

Z-100
  • 518
  • 3
  • 19
  • 1
    Thanks for the tip. I would like to solve this "properly", but if it comes down to it I will make use of this. – bezbos. Feb 22 '23 at 10:54
  • worked for me as well! I was wondering if there's less hacky way of solving this. @bezbos, did you manage to figure it out? – Taiyr Begeyev Mar 11 '23 at 23:42
  • @TaiyrBegeyev sadly no... instead we decided to use Spring Resource Server and Keycloak, which is complete overkill for this project, but we have no other choice. Idk what Spring Security team messed up, but it's not even funny at this point. – bezbos. Mar 13 '23 at 07:50