0

I have two sets of endpoints: /api/** and /ui/**, each one will have a separated authentication mechanism. For the sake of the simplification for this question: both will basically use JWT but with different secrets. I have the below configuration:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig {

    @Order(1)
    @Configuration
    public static class SecurityConfigApi {

        @Autowired
        private CustomUserDetailsService userDetailsService;

        @Autowired
        private UnauthorizedHandler unauthorizedHandler;

        @Autowired
        private PasswordEncoder passwordEncoder;

        @Bean
        public AuthTokenFilterApi authenticationJwtTokenFilterApi() {
            return new AuthTokenFilterApi();
        }

        @Bean
        public DaoAuthenticationProvider authenticationProviderApi() {
            DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

            authProvider.setUserDetailsService(userDetailsService);
            authProvider.setPasswordEncoder(passwordEncoder);

            return authProvider;
        }

        @Bean
        public AuthenticationManager authenticationManagerApi(
                AuthenticationConfiguration authConfig) throws Exception {
            return authConfig.getAuthenticationManager();
        }


        @Bean
        public SecurityFilterChain filterChainApi(HttpSecurity http) throws Exception {
            http.antMatcher("/api/**")
                    .cors()
                    .and()
                    .csrf()
                    .disable()
                    .exceptionHandling()
                    .authenticationEntryPoint(unauthorizedHandler)
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/api/**").authenticated();

            http.addFilterBefore(authenticationJwtTokenFilterApi(), UsernamePasswordAuthenticationFilter.class);

            return http.build();
        }
    }

    @Order(2)
    @Configuration
    public static class UiSecurityConfig {

        @Autowired
        private CustomUserDetailsService userDetailsService;

        @Autowired
        private UnauthorizedHandler unauthorizedHandler;

        @Autowired
        private PasswordEncoder passwordEncoder;

        @Bean
        public AuthTokenFilter authenticationJwtTokenFilterUi() {
            return new AuthTokenFilter();
        }

        @Bean
        public DaoAuthenticationProvider authenticationProviderUi() {
            DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

            authProvider.setUserDetailsService(userDetailsService);
            authProvider.setPasswordEncoder(passwordEncoder);

            return authProvider;
        }

        @Bean
        public AuthenticationManager authenticationManagerUi(
                AuthenticationConfiguration authConfig) throws Exception {
            return authConfig.getAuthenticationManager();
        }

        @Bean
        public SecurityFilterChain filterChainUi(HttpSecurity http) throws Exception {
            http.antMatcher("/ui/**")
                    .cors()
                    .and()
                    .csrf()
                    .disable()
                    .exceptionHandling()
                    .authenticationEntryPoint(unauthorizedHandler)
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/ui/auth/login")
                    .permitAll()
                    .antMatchers("/ui/auth/signup")
                    .hasRole("ADMIN")
                    .antMatchers("/ui/auth/refresh-token")
                    .hasAnyRole();

            http.addFilterBefore(authenticationJwtTokenFilterUi(), UsernamePasswordAuthenticationFilter.class);

            return http.build();
        }
    }
}

And the filter (you can assume the other one is similar to this):

public class AuthTokenFilterApi extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilterApi.class);

    @Autowired
    private JwtUtilsApi jwtUtils;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = jwtUtils.getJwtFromHttpRequest(request);
            if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
                String username = jwtUtils.getUserNameFromJwtToken(jwt);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails,
                                null,
                                userDetails.getAuthorities());

                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            logger.error("Cannot set user authentication.", e);
        }
        filterChain.doFilter(request, response);
    }
}

It seems to be working OK, the calls are getting correctly authenticated by the correct filter but I noticed that after the authentication happens, the filter for the other enpoint is still being executed.

Is there a way to prevent this? Is this the correct behavior of Spring Security? I can always see both filters when I inspect the FilterChain but I thought that as I have two different configured security contexts, that would not happen.

Ventrue
  • 372
  • 3
  • 17
  • try creating two filter chains with `@Order(1)` on one of them (main), rather than creating two different configurations. the example is [here](https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#_multiple_httpsecurity), – Andrei Titov Aug 31 '22 at 16:42
  • @AndrewThomas Thanks, but the behavior keeps the same with this approach. – Ventrue Aug 31 '22 at 17:12
  • 1
    what is the reason for you not using the built in JWTFilter that comes with spring security? – Toerktumlare Aug 31 '22 at 19:02
  • I need to make some validations and checks in the JWT token. I added a simple version of the filter above. – Ventrue Aug 31 '22 at 19:27
  • `I need to make some validations and checks in the JWT token` which you can with no problem do using the built in filter that comes with spring security. So im going to ask you again, what is the reason for not using the built in filter in spring security, and why didnt you read the spring security documentation on how to handle jwts before asking here on stack overflow? – Toerktumlare Aug 31 '22 at 23:37
  • 2
    here is a spring decurity JWT example project that uses custom validation etc https://github.com/Tandolf/spring-security-jwt-demo please – Toerktumlare Aug 31 '22 at 23:38

0 Answers0