티스토리 뷰
SpringBoot 로 RestAPI 를 개발하다 보면, 클라이언트에 일관된 에러 응답을 제공하기 위해 예외처리의 표준화가 필요합니다. 이를 효율적으로 관리하기 위해 RestErrorAdvice 를 설정하면 각종 예외를 깔끔하게 처리할 수 있습니다. 이번 포스팅에선 RestErrorAdvice 를 설정하는 방법에 대해 소개하겠습니다.
1. Dependency 추가
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-web'
...
}
- gradle 또는 maven 에 Dependency 를 추가합니다.
2. 공통 에러 응답 포맷 정의
@Getter
@Setter
@NoArgsConstructor
public class BaseResponse {
private int status = 200;
private String message = "SUCCESS";
private String code = "OK";
}
- 이번 예제에서는 위처럼 간단한 상태와 메세지정도만 출력하도록 포맷을 정의하였습니다.
3. ExceptType 작성
@Getter
@RequiredArgsConstructor
@JsonFormat(shape = JsonFormat.Shape.OBJECT) // Enum 클래스 한글사용
public enum ExceptType {
// 시스템 에러
SYSTEM_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "존재하지 않는 API 입니다."),
USER_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "사용자가 존재하지 않습니다."),
USER_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증에 실패하였습니다."),
USER_LOGIN_NOT_MATCH(HttpStatus.UNAUTHORIZED, "ID 또는 비밀번호가 일치하지 않습니다."),
USER_PERMISSION_DENIED(HttpStatus.UNAUTHORIZED, "권한이 부족합니다."),
TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다."),
TOKEN_INCORRECT(HttpStatus.UNAUTHORIZED, "올바르지 않은 토큰입니다."),
TOKEN_INVALID(HttpStatus.UNAUTHORIZED, "토큰이 유효하지 않습니다."),
TOKEN_SIGNATURE(HttpStatus.UNAUTHORIZED, "서명이 올바르지 않습니다."),
INTERNAL_SERVER(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다.");
private final HttpStatus status;
private final String message;
}
- 기본 에러메시지 대신 응답할 메세지 코드와 내용을 정의합니다.
- Enum Class 를 통하여 깔끔하게 관리할 수 있습니다.
4. Custom Exception 작성
@Getter
public class MyApiException extends RuntimeException {
private ExceptType error;
public MyApiException(ExceptType e) {
super(e.getMessage());
this.error = e;
}
}
- 시스템 기본 예외 이외의 Custom 예외를 처리할 소스를 작성합니다.
5. EntryPoint 작성
@Slf4j
@Component
public class MyEntryPoint implements AuthenticationEntryPoint {
private final HandlerExceptionResolver resolver;
public MyEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
log.warn("Authentication failed for request to {}: {}", request.getRequestURI(), authException.getMessage());
resolver.resolveException(request, response, null, authException);
}
}
- SpringSecurity 에서 인증 실패 시 처리하는 엔트리 포인트를 지정합니다.
- 기본적으로 인증 실패 시 Unauthroized 응답을 반환하지만, 이 과정을 커스터마이징하여 로그를 남기거나 사용자 정의 에러 메시지를 반환할 수 있습니다.
6. Security 설정 파일 수정
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
...
.exceptionHandling(handler -> handler.authenticationEntryPoint(myEntryPoint))
...
.build();
}
- Security 설정파일에 EntryPoint 를 ExceptionHandling 에 추가합니다.
7. RestErrorAdvice 작성
/**
* Rest Error 공통 처리 클래스
*/
@Slf4j
@RestControllerAdvice
public class MyRestErrorAdvice {
/**
* Custom Exception 처리
*/
@ExceptionHandler(MyApiException.class)
public ResponseEntity<BaseResponse> handleMyApiException(MyApiException ex) {
return makeResponse(ex.getError());
}
/**
* 처리되지 않은 모든 예외(Exception) 처리
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<BaseResponse> handleAllExceptions(Exception ex) {
return makeResponse(ExceptType.INTERNAL_SERVER, ex.getMessage());
}
/**
* 404 Not Found 처리
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<BaseResponse> handleNotFound(NoHandlerFoundException ex) {
return makeResponse(ExceptType.SYSTEM_NOT_FOUND_EXCEPTION);
}
/**
* 처리되지 않은 런타임 예외 처리
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<BaseResponse> handleRuntimeException(RuntimeException ex) {
return makeResponse(ExceptType.INTERNAL_SERVER, ex.getMessage());
}
/**
* JWT 서명(Signature) 관련 예외 처리
*/
@ExceptionHandler(SignatureException.class)
public ResponseEntity<BaseResponse> handleSignatureException(SignatureException ex) {
return makeResponse(ExceptType.TOKEN_SIGNATURE);
}
/**
* 잘못된 형식의 JWT가 전달될 때 발생하는 예외 처리
*/
@ExceptionHandler(MalformedJwtException.class)
public ResponseEntity<BaseResponse> handleMalformedJwtException(MalformedJwtException ex) {
return makeResponse(ExceptType.TOKEN_INCORRECT);
}
/**
* 만료된 JWT 토큰에 대한 예외 처리
*/
@ExceptionHandler(ExpiredJwtException.class)
public ResponseEntity<BaseResponse> handleExpiredJwtException(ExpiredJwtException ex) {
return makeResponse(ExceptType.TOKEN_EXPIRED);
}
/**
* 인증 실패(AuthenticationException)에 대한 예외 처리
*/
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<BaseResponse> handleAuthException(AuthenticationException ex) {
return makeResponse(ExceptType.USER_LOGIN_NOT_MATCH);
}
/**
* 권한 부족(AccessDeniedException)에 대한 예외 처리
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<BaseResponse> handleAccessDeniedException(AccessDeniedException ex) {
return makeResponse(ExceptType.USER_PERMISSION_DENIED);
}
/**
* 응답 공통 처리 메서드 (기본 메시지 사용)
*/
private ResponseEntity<BaseResponse> makeResponse(ExceptType exceptType) {
return makeResponse(exceptType, exceptType.getMessage());
}
/**
* 응답 공통 처리 메서드 (사용자 지정 메시지 지원)
*/
private ResponseEntity<BaseResponse> makeResponse(ExceptType exceptType, String message) {
BaseResponse response = new BaseResponse();
response.setStatus(exceptType.getStatus().value()); // HTTP 상태 코드 설정
response.setCode(exceptType.name()); // 예외 유형 코드 설정
response.setMessage(message); // 예외 메시지 설정
return ResponseEntity.status(exceptType.getStatus()).body(response);
}
}
- 필요한 응답을 작성합니다.
8. 사용
public User updateUser(UserRequest userRequest) {
User user = userRepository.findById(userRequest.getUserId())
.orElseThrow(() -> new MyApiException(ExceptType.USER_LOGIN_NOT_MATCH));
return userRepository.save(user);
}
{
"status": 401,
"message": "ID 또는 비밀번호가 일치하지 않습니다.",
"error": "USER_UNAUTHORIZED"
}
- 직접 정의 또는, Runtime Exception 으로 발생한 예외들을 원하는 포맷에 맞게 처리할 수 있습니다.
감사합니다.
'프레임워크 > SpringBoot' 카테고리의 다른 글
[SpringBoot] 문자열과 파일 동시에 업로드하기(With Vue) (0) | 2025.03.28 |
---|---|
[SpringBoot] RabbitMQ 튜토리얼 (0) | 2025.03.10 |
[SpringBoot] Docker 배포 가이드 (2) | 2024.12.30 |
[SpringBoot] JPA + GraphQL 연동 예제 (2) | 2024.12.23 |
[Spring] AOP 의 주요 사용처 (1) | 2024.12.03 |
최근에 올라온 글
- Total
- Today
- Yesterday