Role based restriction in requestMatchers in Spring Security does not receive sent Authorization header

20 Views Asked by At

I am trying to implement Spring Security with securityFilterChain, that uses requestMatchers. When I set it to permitAll() everything works, but when I restrict by role, then authorization headers are received as missing in the JwtAuthenticationFilter. Security configuration code:

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final JwtAuthenticationFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;
    private final LogoutHandler logoutHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)// TODO check how this should be in prod
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("api/v1/auth/**").permitAll()// here should be no business logic besides auth functionality to allow this
                    .requestMatchers("api/v1/users/**").hasRole(USER.name())
                    .requestMatchers("api/v1/observer/*").hasRole(OBSERVER.name())
                    .requestMatchers("api/v1/user/*").hasRole(USER.name())
                    .requestMatchers("api/v1/admin/*").hasRole(ADMIN.name())
                    .anyRequest()
                    .authenticated()
            )
                .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .logout(logout ->
                        logout.logoutUrl("/api/v1/auth/logout")
                        .addLogoutHandler(logoutHandler)
                        .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext())
                )
        ;
         return http.build();
    }
}

Authentication filter code:

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;
    private final TokenRepository tokenRepository;


    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request,
                                    @NonNull HttpServletResponse response,
                                    @NonNull FilterChain filterChain)
            throws ServletException, IOException {
        if (request.getServletPath().contains("/api/v1/auth")) {
            filterChain.doFilter(request, response);
            return;
        }
        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String userName;
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }
        jwt = authHeader.substring(7);
        userName = jwtService.extractUserName(jwt);
        if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName);
            var isTokenValid = tokenRepository.findByToken(jwt)
                    .map(t -> !t.isExpired() && !t.isRevoked())
                    .orElse(false);
            if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        userDetails,
                        null,
                        userDetails.getAuthorities()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)
                );
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

Also frontend gets CORS error, that I see from the Network tab in the browser, when hasRole() restriction is added, but not when permitAll() is used. I also see, that bearer is sent.

I expected to get bearer token in both situations, as user should have correct role set, but it worked only with permitAll(). I checked that controllers have @CrossOrigin correctly added. When debugging, the final String authHeader is empty when role is required, other ways it is received nicely.

0

There are 0 best solutions below