package com.project.bookforeast.common.domain.error;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.project.bookforeast.common.security.error.TokenErrorResult;
import com.project.bookforeast.common.security.error.TokenException;
import com.project.bookforeast.user.error.UserErrorResult;
import com.project.bookforeast.user.error.UserException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// 클라이언트에서 파라미터 잘못 전달한 경우
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
// exception으로 부터 에러를 가져와서 list에 담는다.
final List<String> errorList = ex.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
// 해당 에러메세지 로그를 찍는다.
log.warn("클라이언트로부터 잘못된 파라미터 전달됨 : {}", errorList);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(HttpStatus.BAD_REQUEST.value(), errorList.toString()));
}
// 사용자 정의 excepion이 발생한 경우
// userException
@ExceptionHandler({UserException.class})
public ResponseEntity<ErrorResponse> handleUserException(final UserException exception) {
log.warn("UserException occur:" + exception);
UserErrorResult errorResult = exception.getUserErrorResult();
return ResponseEntity.status(errorResult.getStatus())
.body(new ErrorResponse(errorResult.getStatus().value(), errorResult.getMessage()));
}
// tokenException
@ExceptionHandler({TokenException.class})
public ResponseEntity<ErrorResponse> handleTokenException(final TokenException exception) {
log.warn("TokenException occur:" + exception);
TokenErrorResult errorResult = exception.getTokenErrorResult();
return ResponseEntity.status(errorResult.getStatus())
.body(new ErrorResponse(errorResult.getStatus().value(), errorResult.getMessage()));
}
@RequiredArgsConstructor
@Getter
static class ErrorResponse {
private final int code;
private final String message;
}
}
그 이유는 @RestControllerAdvice때문이었다. 이 어노테이션으로 인해서 이 globalExceptionHandler는 restController의 로직을 타는 중 발생하는 에러만 처리해 주고 있었다.
package com.project.bookforeast.common.domain.error;
import java.io.IOException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.bookforeast.common.domain.error.GlobalExceptionHandler.ErrorResponse;
import com.project.bookforeast.common.security.error.TokenErrorResult;
import com.project.bookforeast.common.security.error.TokenException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (TokenException tokenException) {
log.warn("TokenException occur:");
TokenErrorResult errorResult = tokenException.getTokenErrorResult();
setErrorResponse(errorResult.getStatus(), errorResult.getMessage() ,response);
}
}
private void setErrorResponse(HttpStatus status, String message, HttpServletResponse response) {
response.setStatus(status.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
ErrorResponse errorResponse = new ErrorResponse(status.value(), message);
try {
String json = new ObjectMapper().writeValueAsString(errorResponse);
response.getWriter().write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.project.bookforeast.common.security.error;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class TokenException extends RuntimeException {
private final TokenErrorResult tokenErrorResult;
}
package com.project.bookforeast.common.security.error;
import org.springframework.http.HttpStatus;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum TokenErrorResult {
TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다."),
ACCESS_TOKEN_NEED(HttpStatus.UNAUTHORIZED, "엑세스 토큰이 필요합니다."),
REFRESH_TOKEN_NEED(HttpStatus.UNAUTHORIZED, "리프레시 토큰이 필요합니다."),
TOKEN_EMPTY(HttpStatus.UNAUTHORIZED, "토큰을 전달해주세요")
;
private final HttpStatus status;
private final String message;
}