Post

[Spring] TaskFlow 트러블슈팅 기록

TaskFlow 과제를 진행하면서 겪은 트러블슈팅의 과정들에 대한 기록입니다.
해당 프로젝트의 전체소스는 여기 에서 확인하실 수 있습니다.

트러블슈팅


⭐️ 주제

@PreAuthorize 권한 실패 시 500 에러 발생
AccessDeniedHandler가 아닌 GlobalExceptionHandler가 호출되었을까?

🔥 발생

  • 권한이 없는 사용자가 @PreAuthorize가 붙은 API를 호출
1
2
3
4
5
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN')")
public @interface AdminOnly{
}
1
2
3
4
5
6
7
8
9
10
11
12
@AdminOnly // 관리자만 접근 가능
@PostMapping("/api/teams") // 해당 API를 호출
@ResponseStatus(HttpStatus.CREATED)
public CommonResponse<CreateTeamResponse> createTeam(
    @Valid @RequestBody
    CreateTeamRequest createTeamRequest) {
    CreateTeamResponse createTeamResponse = createTeamUseCase.execute(
        createTeamRequest.name(),
        createTeamRequest.description());

    return CommonResponse.success("팀이 성공적으로 생성되었습니다.", createTeamResponse);
}
  • 기대: 403 Forbidden
  • 실제: 500 Internal Server Error
  • CustomAccessDeniedHandler 로그가 찍히지 않음
  • 대신 GlobalExceptionHandler가 모든 예외를 받아 JSON 변환 실패 → 500 발생

🔍 원인

  1. 예외 처리 경로 차이

    • URL 기반 인가(authorizeHttpRequests) → Security Filter Chain 단계에서 실패 → AccessDeniedHandler가 처리
    • 메서드 기반 인가(@PreAuthorize) → 컨트롤러 진입 직전 AOP 단계에서 실패 → Spring MVC 예외 처리 흐름 → GlobalExceptionHandler가 처리
  2. 직렬화 오류

    • GlobalExceptionHandlerHttpStatus 객체를 응답 바디에 직접 담음
    • Jackson이 HttpStatus 객체를 JSON으로 변환하지 못해 500 에러 발생

✅ 해결

  • GlobalExceptionHandler에서 AccessDeniedException을 명시적으로 처리하도록 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    // ... 생략 ...

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<CommonResponse<?>> handleAccessDeniedException(AccessDeniedException ex) {
        ErrorCode errorCode = AuthErrorCode.FORBIDDEN;
        HttpStatus status = errorCode.getHttpStatus();
        String message = errorCode.getMessage();
        return new ResponseEntity<>(CommonResponse.failure(message, null), status);
    }

    // ... 생략 ...
}

💡 결론

  • @PreAuthorize에서 발생하는 권한 실패 예외는 보안 필터 체인 밖에서 발생하므로 AccessDeniedHandler가 아닌 GlobalExceptionHandler가 처리한다.
  • 따라서 권한 예외는 GlobalExceptionHandler에서 명시적으로 처리해야 한다.

© Hoon. Some rights reserved.