[Spring] GlobalExceptionFilter 에 적용한 Exception 잡히지 않는 오류
1. 문제상황
AOP 를 활용해서 요청의 권한을 검증하고
권한이 없는 경우 AccessDenieedException 을 던지고
GlobalExceptionHandler 에서 잡아서 처리하길 기대했다.
문제의 코드 ( 권한 검증 AOP )
@Before("@annotation(requireBookAuthority)")
public void verifyAttendanceBookAuthority(JoinPoint joinPoint, RequiredBookAuthority requireBookAuthority) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 1. 요청에서 attendanceBookId 파싱
Long attendanceBookId = parseAttendanceBookId(request);
if (attendanceBookId == null) {
throw new IllegalArgumentException("attendanceBookId가 요청에 포함되어야 합니다.");
}
// 2. 사용자 권한 조회
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<AttendanceBookAuthority> userAuthorities = userAuthorityService.getUserAuthorities(userId);
// 3. 어노테이션에 설정된 requiredRole 이상의 권한이 있는지 검증
AttendanceRole requiredRole = requireBookAuthority.requiredRole();
boolean hasAuthority = userAuthorities.stream()
.anyMatch(auth ->
auth.bookId().equals(attendanceBookId) && auth.role().isRoleLevelGteThan(requiredRole)
);
if (!hasAuthority) {
throw new AccessDeniedException("요청된 출석부 ID = " + attendanceBookId + " 에 대한 권한이 없습니다.");
}
}
GlobalExceptionHandler 코드
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(AccessDeniedException.class)
public ErrorResponse handleAccessDeniedException(AccessDeniedException ex) {
log.error("Access Denied Exception Occurred", ex);
ErrorResponse response = new ErrorResponse(HttpStatus.FORBIDDEN.value(), "Access Denied Exception", ex.getMessage());
return response;
}
2. 에러 분석
권한이 없는 API 를 요청해보자
예상 결과는 status는 403, message 에는 Access Denied Exception 이 나오길 기대한다.
근데 이게 무슨 일 ? 왜 ㄴstatus = 500 으로 나오는 걸까 .. 심지어 요청의 상태 코드는 200 이다..
에러 메시지를 살펴보자
먼저 로그를 보면 우리가 원하던 GlobalExceptionHandler 까지는 에러가 도달한 것으로 보인다.
그런데 처리한 에러가 UndeclaredThrowableException 이다.
분명 AccessDeniedException 을 던졌는데 왜 이렇게 된 걸까 ?
조금 더 살펴보자
아래로 내려보니 AccessDeniedException 을 던졌고, 정상적으로 에러 메시지도 전달했다.
3. Spring 이 제공하는 에러 사용해 ! 제발 !
문제 상황은 이렇다.
AOP 에서 AccessDeniedException 제대로 던졌음
-> 누군가가 UndeclaredThrowableException 로 치환함
-> GlobalExceptionHandler 에서 못 잡음
뭔가 이상하다 ...
그리고 다시 코드를 보니
읭 ...? 누가 이런 에러 클래스를 사용하고 있는 것이니 ?
제발 ... Spring 에서 제공하는 Exception 사용하자 ...
Spring 에서 제공하는 클래스로 변경한 후에 다시 요청을 보내보자
원하는 결과가 나오는 것을 확인할 수 있다 !
4. 근데 왜 ?
근데 왜 이런 현상이 벌어질까 ?
Spring MVC의 예외 처리 메커니즘은 보통 RuntimeException 기반으로 예외를 처리하거나
Spring 자체에서 정의한 예외들에 대해 동작한다.
그런데 java.nio.file.AccessDeniedException은 타고타고 들어가보면 FileSystemException 이 나온다.
이는 Java 표준이 제공하는 Exception 이기 때문에 Spring에서는 처리하지 못하는 에러가 되어
UndeclaredThrowableException 로 변경이 된 것이다.