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

[Dúvida] Qualquer exception retorna 403

Estou enfrentando um problema em um projeto de estudos. Ao lidar com requisições para um Controller não mapeado, o sistema está retornando um código de status 403 - Forbidden, em vez de 404 ou Bad Request, como é o esperado.

Ao habilitar o logging do Spring Security, identifiquei que o erro ocorre no método doFilter(). Gostaria de saber como posso prevenir esse comportamento indesejado e aplicar boas práticas nesse contexto.

Além disso, observei que, em um cenário específico (Caso 2), quando é enviado um Bearer token e o sistema faz uma consulta no banco para verificar a autenticidade, a rota nem mesmo existe, e o sistema não verifica se a rota está mapeada antes de tentar autenticar. Isso para mim é errado e não deveria ocorrer?

Você pode encontrar o código-fonte relevante neste repositório: https://github.com/team-off-app/team-off-api. A classe SecurityFilter está em com/teamoff/api/infra/security/SecurityFilter.java

Aqui está a implementação da classe SecurityFilter, seguida pelos logs da aplicação:

@Component
public class SecurityFilter extends OncePerRequestFilter {

    private final TokenService tokenService;
    private final AuthRepository authRepository;

    public SecurityFilter(TokenService tokenService, AuthRepository authRepository) {
        this.tokenService = tokenService;
        this.authRepository = authRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String tokenJWT = getToken(request);
        if (tokenJWT != null) {
            String authID = tokenService.getClaim(tokenJWT, "auth_id");
            Auth auth = authRepository.findAuthById(UUID.fromString(authID));

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(auth, null, auth.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String getToken(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null) {
            return authHeader.replace("Bearer ", "");
        }
        return null;
    }
}

Logs do Caso 1 (Requisição para endpoint não mapeado /api/events, sem Bearer Token): Logs - Caso 1

Logs do Caso 2 (Requisição para endpoint não mapeado /api/events, com Bearer Token): Logs - Caso 2

Agradeço antecipadamente pela ajuda!

5 respostas

Oi Matheus!

Dá uma olhada nesse tópico que discutiu a mesma situação: https://cursos.alura.com.br/forum/topico-login-212219

Bons estudos!

Opa, muito obrigado. Funcionou parcialmente. Mas para outras exceptions continua dando 403 Forbidden ao invés de dar um Bad Request ou 500.

Neste exemplo eu propositalmente coloquei as datas fora do formato. Insira aqui a descrição dessa imagem para ajudar na acessibilidadeA Exception que estourou foi que ele não conseguiu transformar a String em Data.

Hibernate: select a1_0.id,a1_0.login,a1_0.password,a1_0.user_id from auth a1_0 where a1_0.id=?
Hibernate: select u1_0.id,u1_0.name,u1_0.photo_url from users u1_0 where u1_0.id=?
2023-12-27T09:26:23.009-03:00  WARN 13296 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String "2023-12-27T12:25:20+0000": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2023-12-27T12:25:20+0000' could not be parsed, unparsed text found at index 19]

Seguem os métodos do meu Exception Handler:

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Map<String, String> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return errors;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    public Map<String, String> handleMethodArgumentTypeMismatchException(
            MethodArgumentTypeMismatchException ex) {
        Map<String, String> error = new HashMap<>();
        error.put(ex.getName(), ex.getMessage());
        return error;
    }

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler({UserNotFoundException.class})
    public Map<String, String> handleUserNotFoundException(
            UserNotFoundException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return error;
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<Object> handleSqlException (DataIntegrityViolationException ex){
        return new ResponseEntity<>(HttpStatus.CONFLICT);
    }

    @ExceptionHandler({EntityNotFoundException.class, NoHandlerFoundException.class})
    public ResponseEntity<Object> tratarErro404() {
        return ResponseEntity.notFound().build();
    }
solução!

Coloca mais um tratamento para a exception que ocorreu e outro para exception no geral:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class})
public Map<String, String> handleMessageNotReadableException(
        HttpMessageNotReadableException ex) {
    Map<String, String> error = new HashMap<>();
    error.put(ex.getName(), ex.getMessage());
    return error;
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({Exception.class})
public Map<String, String> handleException(
        Exception ex) {
    Map<String, String> error = new HashMap<>();
    error.put(ex.getName(), ex.getMessage());
    return error;
}

Funcionou. No caso se eu não colocar tratar o Exception ele vai continuar estourando o 403 mesmo. Saberia dizer o porquê?

O Spring Security tem um tratamento de exceptions separado e quando uma exception não é tratada, ela acaba sendo capturada por ele e por isso volta como erro 403.