Solucionado (ver solução)
Solucionado
(ver solução)
8
respostas

Exceção não capturada pelo ExceptionHandler

Estou tentando personalizar os status de erro da API, por exemplo, quando é passado um token expirado, a resposta está sendo 403 Forbidden. A exceção é exibida no console porém o ExceptionHandler não a captura e a API retorna 403.

Insira aqui a descrição dessa imagem para ajudar na acessibilidadeInsira aqui a descrição dessa imagem para ajudar na acessibilidade

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Como posso resolver?

8 respostas

Olá Arthur!

Poderia compartilhar seu projeto via um link do github?

Olá, Otávio. Perdão pela demora, eu iniciei um outro projeto, com a mesma ideia do curso, e ainda assim tenho a mesma situação. Se tu puder dar uma olhada:

Notei que mais pessoas estão com o mesmo problema e a sugestão é sempre a mesma, e ainda assim não consigo resolver kkkk estou com bastante duvida em relação a isso. Valeu

Oi Arthur!

Como a exception foi lançada dentro do Filter, acaba que ela não será capturada pelo tratador de erros.

Você pode alterar o código do filter para fazer o tratamento:

try {
    var tokenJWT = recuperarToken(request);

    if (tokenJWT != null) {
        var subject = tokenService.getSubject(tokenJWT);
        var usuario = repository.findByLogin(subject);

        var authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
} catch (Exception ex) {
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.setCharacterEncoding("UTF-8");
    response.getWriter().println(ex.getLocalizedMessage());
}

Olá, Rodrigo! Obrigado pela resposta, vou fazer essa implementação. Tenho mais um dúvida, o Spring Security captura muitas da exceções lançadas né? Por exemplo, se mandar uma requisição para um endpoint que não existe, geralmente o Spring retornaria um erro 404 porém nessa implementação do Spring Security acaba sempre retornando 403. Teria como resolver isso? Talvez não essas exceções em específico mas outras que podem vir a aparecer e que mantenham a API com retornos coerentes.

Obrigado pela ajuda!

solução!

Essa questão é chatinha no Spring Security :D

Para tratar tudo certinho você vai precisar fazer o seguinte:

  1. Alterar o padrão do Spring para URLs não mapeadas, no arquivo application.properties:

     spring.mvc.throw-exception-if-no-handler-found=true
     spring.web.resources.add-mappings=false
  2. Alterar o tratamento de erro 404 na classe TratadorDeErros, para incluir a Exception do tipo NoHandlerFoundException:

     @ExceptionHandler({EntityNotFoundException.class, NoHandlerFoundException.class})
     public ResponseEntity tratarErro404() {
         return ResponseEntity.notFound().build();
     }
  3. Sobrescrever o tratamento de erros de segurança do Spring Security, na classe SecurityConfigurations:

     @Bean
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         return http.csrf().disable()
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                 .and().authorizeHttpRequests()
                 .requestMatchers(HttpMethod.POST, "/login").permitAll()
                 .requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll()
                 .anyRequest().authenticated()
                 .and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
    
                  //sobrescrevendo tratador de erros do Security:
                 .exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler())
                 .and().build();
     }
    
     private class UnauthorizedHandler implements AuthenticationEntryPoint {
    
         @Override
         public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
         }
     }
    
     //resto do codigo da classe...

Com isso vai funcionar conforme o esperado.

Olá, Rodrigo Fiz esse teste porém dessa forma retorna 401. O fluxo chega no método commence e retorna o erro SC_UNAUTHORIZED.

Se acessar uma URL não mapeada sem estar logado (sem enviar um token válido) será devolvido 401 mesmo, pois vai bater no Spring Security de que você está tentando disparar uma requisição que é restrita, mas sem estar logado.

Somente se estiver logado e enviar uma requisição para uma URL não mapeada é que vai ser retornado 404.

O raciocínio é esse:

.requestMatchers(HttpMethod.POST, "/login").permitAll() -> URL de login não precisa estar logado

.requestMatchers("/v3/api-docs/*", "/swagger-ui.html", "/swagger-ui/*").permitAll() -> URLs do swagger não precisa estar logado

.anyRequest().authenticated() -> Qualquer outra URL precisa estar logado para acessar (independente se a URL estiver mapeada ou não)