-1

How can I set up two Spring Security filter chains, first with a JWT filter (/private) and the other without a JWT filter (/public).

The expectation is that when the URI matches the first filter (/private) the JWT filter should kick in and for a match to the second one (/public), no calls should be made to the JWT filter.

Here is the config:

1st Filter Chain

@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {

    return http
               .securityMatcher("/private/**")
               .csrf(AbstractHttpConfigurer::disable)
               .cors((t) -> {
                   t.configurationSource((corsConfigurationSource()));
               })
               .authorizeHttpRequests(auth -> {
                   auth.requestMatchers("/error/**").permitAll();
                   auth.anyRequest().authenticated();
               })
               .sessionManagement(session -> session
                  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               )
               .addFilterBefore(jwtTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
               .exceptionHandling()
                   .authenticationEntryPoint(authFailureHandler)
                   .and()
               .exceptionHandling()
                   .accessDeniedHandler(authFailureHandler)
                   .and()
               .build();
}

2nd Filter Chain

There is not filter chain configured here, but when an user passes an Authorization header Spring Security invokes the jwtTokenAuthenticationFilter. Why?

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain publicApiFilterChain(HttpSecurity http) throws Exception {

    return http
               .securityMatcher("/public/**")
               .csrf(AbstractHttpConfigurer::disable)
               .cors((t) -> {
                   t.configurationSource((corsConfigurationSource()));
               })
               .authorizeHttpRequests(auth -> {
                   auth.requestMatchers("/error/**").permitAll();
                   auth.anyRequest().authenticated();
               })
               .sessionManagement(session -> session
                   .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
               .exceptionHandling()
                   .accessDeniedHandler(authFailureHandler)
                   .and()
               .exceptionHandling()
                   .authenticationEntryPoint(authFailureHandler)
                   .and()
               .build();
}

Here is the JWT Filter Class

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenUtil jwtTokenUtil;
    private final AdministratorDetailService administratorDetailService;

    private final AuthFailureHandler authFailureHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        try {
            // Get authorization header and validate
            final String header = request.getHeader(HttpHeaders.AUTHORIZATION);

            if (StringUtils.isBlank(header) || !header.startsWith("Bearer ")) {
                chain.doFilter(request, response);
                return;
            }

            // Get jwt token and validate
            final String token = header.split(" ")[1].trim();

            log.info("validating token...");

            if (!jwtTokenUtil.validateToken(token)) {
                chain.doFilter(request, response);
                return;
            }

            String username = jwtTokenUtil.getUsername(token);
            String userType = jwtTokenUtil.getUserType(token);

            if (StringUtils.isBlank(username) || StringUtils.isBlank(userType)) {
                log.debug("can not resolve username and usertype...");
                chain.doFilter(request, response);
                return;
            }

            UserDetails details = administratorDetailService.loadUserByUsername(username);

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            log.info("setting up the principle object on SecurityContext...");

            SecurityContextHolder.getContext().setAuthentication(authentication);

            chain.doFilter(request, response);
        } catch (MalformedJsonException | UsernameNotFoundException | SecurityException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException | ServletException ex) {
            log.error(ex.getLocalizedMessage());
            authFailureHandler.commence(request, response, new AuthenticationException(ex.getLocalizedMessage()) {
            });
        }
    }
}

1 Answers1

0

Spring Boot registers any Bean in the Servlet Container, so in order for this Filter to be registered with Spring Security, we have to disable it from the Servlet Container. See fix below:

@Bean
public FilterRegistrationBean<JwtTokenAuthenticationFilter> registerFilter(JwtTokenAuthenticationFilter jwtTokenAuthenticationFilter) {
    FilterRegistrationBean<JwtTokenAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>(jwtTokenAuthenticationFilter);
    filterRegistrationBean.setEnabled(false);
    return filterRegistrationBean;
}