Search code examples
javaspringspring-bootspring-securityjwt

After permitting All to a route, stil getting forbidden in Spring securoty


I'm using spring boot 3.2.0. I'm trying to to JWT Based Authentication wth spring security. For that I have defined a JWT based Authentication filter. The problem is there is a route /auths/public/**, I want to permit all to this route. For this in the security config I have done:

    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class SecurityConfiguration {
        private final JwtAuthenticationFilter jwtAuthenticationFilter;
        private final AuthenticationProvider authenticationProvider;
    
        @Bean
        SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.cors(AbstractHttpConfigurer::disable).authorizeHttpRequests((authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
                                    .requestMatchers("/auths/inner/**").hasRole(Role.INNER_SERVICE.name()) // Selectively permitted for users with role INNER_SERVICE with authentication
                                    .requestMatchers("/auths/authenticated/**").hasRole(Role.USER.name()) // Selectively permitted for users with role USER with authentication
                                    .requestMatchers("/auths/public/**").permitAll() // All requests are permitted
                                    .requestMatchers("/actuator/**").permitAll() // All requests are permitted
                                    .anyRequest().permitAll() // All other routes are permitted (!! security alert)
            ))
                    .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                    .authenticationProvider(authenticationProvider)
                    .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
            return http.build();
        }
    }

This is the JWT custom filter I have written:

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String userEmail;
        // for public urls
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }
        // To cut out the "Bearer " part
        jwt = authHeader.substring(7);
        userEmail = jwtService.extractUsername(jwt, true);
        if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // user present but not authenticated yet
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
            if (jwtService.isTokenValid(jwt, userDetails, true)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails,
                        null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }

        }
        filterChain.doFilter(request, response);
    }

}

This main part here is this couple of lines:

http.cors(AbstractHttpConfigurer::disable).authorizeHttpRequests((authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
                                    .requestMatchers("/auths/inner/**").hasRole(Role.INNER_SERVICE.name()) // Selectively permitted for users with role INNER_SERVICE with authentication
                                    .requestMatchers("/auths/authenticated/**").hasRole(Role.USER.name()) // Selectively permitted for users with role USER with authentication
                                    .requestMatchers("/auths/public/**").permitAll() // All requests are permitted
                                    .requestMatchers("/actuator/**").permitAll() // All requests are permitted
                                    .anyRequest().permitAll() // All other routes are permitted (!! security alert)
            ))
                    .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                    .authenticationProvider(authenticationProvider)
                    .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
            return http.build();

Here you can see I have permitted all for the route /auths/public but when I do a request like:

POST http://localhost:5000/auths/public/send?message=Hello_Kafka I get:

{
    "timestamp": "2024-01-15T18:42:16.607+00:00",
    "status": 403,
    "error": "Forbidden",
    "message": "Forbidden",
    "path": "/auths/public/send"
}

These are the logs that I get for this request:

2024-01-16 00:12:16.495 
d9814d3abe2031bb
2024-01-16T00:12:16.495+05:30  WARN [auth,0ac98fa25129402e7442fba23c5552be,d9814d3abe2031bb] 47661 --- [auth] [http-nio-auto-1-exec-1] [0ac98fa25129402e7442fba23c5552be-d9814d3abe2031bb] o.s.w.s.h.HandlerMappingIntrospector     : Cache miss for ERROR dispatch to '/error' (previous null). Performing MatchableHandlerMapping lookup. This is logged once only at WARN level, and every time at TRACE.
2024-01-16 00:12:16.550 
afab4ea55ae080f6
2024-01-16T00:12:16.550+05:30 DEBUG [auth,0ac98fa25129402e7442fba23c5552be,afab4ea55ae080f6] 47661 --- [auth] [http-nio-auto-1-exec-1] [0ac98fa25129402e7442fba23c5552be-afab4ea55ae080f6] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for POST "/error?message=Hello_Kafka", parameters={masked}
2024-01-16 00:12:16.558 
afab4ea55ae080f6
2024-01-16T00:12:16.558+05:30 DEBUG [auth,0ac98fa25129402e7442fba23c5552be,afab4ea55ae080f6] 47661 --- [auth] [http-nio-auto-1-exec-1] [0ac98fa25129402e7442fba23c5552be-afab4ea55ae080f6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2024-01-16 00:12:16.619 
afab4ea55ae080f6
2024-01-16T00:12:16.619+05:30 DEBUG [auth,0ac98fa25129402e7442fba23c5552be,afab4ea55ae080f6] 47661 --- [auth] [http-nio-auto-1-exec-1] [0ac98fa25129402e7442fba23c5552be-afab4ea55ae080f6] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2024-01-16 00:12:16.629 
afab4ea55ae080f6
2024-01-16T00:12:16.629+05:30 DEBUG [auth,0ac98fa25129402e7442fba23c5552be,afab4ea55ae080f6] 47661 --- [auth] [http-nio-auto-1-exec-1] [0ac98fa25129402e7442fba23c5552be-afab4ea55ae080f6] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{timestamp=Tue Jan 16 00:12:16 IST 2024, status=403, error=Forbidden, message=Forbidden, path=/auths (truncated)...]
2024-01-16 00:12:16.659 
afab4ea55ae080f6
2024-01-16T00:12:16.659+05:30 DEBUG [auth,0ac98fa25129402e7442fba23c5552be,afab4ea55ae080f6] 47661 --- [auth] [http-nio-auto-1-exec-1] [0ac98fa25129402e7442fba23c5552be-afab4ea55ae080f6] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 403

From the logs I can see the request I'm sending is going to error handler for some reason, I can't understand why.

For the entire codebase: Hangout-Auth-Service, checkout the updated-workflow branch here.

This compose file will help you setup other dependency services

Additional services required to setup:

Service Discovery, checkout the dev branch here.

Application Gateway, checkout the dev branch here.


Solution

  • In the security filter chain disable csrf mainly but here I have disabled cors too for dev mode. This error is mainly coming for csrf not matching because in request I'm not passing any csrf token.

    So, securityFilterChain changes to:

    @Bean
        SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.cors(AbstractHttpConfigurer::disable).csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests((authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
                                    .requestMatchers("/auths/inner/**").hasRole(Role.INNER_SERVICE.name()) // Selectively permitted for users with role INNER_SERVICE with authentication
                                    .requestMatchers("/auths/authenticated/**").hasRole(Role.USER.name()) // Selectively permitted for users with role USER with authentication
                                    .requestMatchers("/auths/public/**").permitAll() // All requests are permitted
                                    .requestMatchers("/actuator/**").permitAll() // All requests are permitted
                                    .anyRequest().permitAll() // All other routes are permitted (!! security alert)
            ))
                    .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                    .authenticationProvider(authenticationProvider)
                    .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
            return http.build();
        }