Bootcamp/Implementation

AuthenticationEntryPoint와 AccessDeniedHandler 예외 처리

개발자 오리 2026. 7. 13. 23:50

💡  오늘 학습 키워드

  • AuthenticationEntryPoint
  • AccessDeniedHandler

 

 

🎯  학습 내용 정리

Spring Security를 적용하면 인증 또는 권한과 관련된 문제가 발생했을 때 자동으로 HTTP 상태 코드를 반환한다.

인증되지 않은 사용자가 보호된 API에 접근하면 401 Unauthorized가 반환되고, 인증은 되었지만 권한이 부족한 사용자가 접근하면 403 Forbidden이 반환된다.

많은 개발자는 이 두 응답을 단순히 상태 코드의 차이로만 이해하지만, Spring Security 내부에서는 서로 다른 예외가 발생하며, 각각을 처리하는 컴포넌트도 다르다.

 

 

예외 처리 방식을 이해하기 위해서는 먼저 인증과 인가를 구분해야 한다.

인증(Authentication)사용자가 누구인지 확인하는 과정이다.

인가(Authorization)인증된 사용자가 특정 자원에 접근할 권한이 있는지 확인하는 과정이다.

 

예를 들어 다음과 같은 API가 있다고 가정한다.

@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
public List<UserResponse> getUsers() {
    ...
}

 

다음 두 상황은 서로 다른 결과를 반환한다.

상황 결과
JWT 없음 401 Unauthorized
JWT 있음 + ROLE_USER 403 Forbidden

 

Spring Security에서 예외는 어디에서 발생할까?

JWT 인증이 완료되면 Spring Security는 AuthorizationFilter에서 권한을 검사한다.

예를 들어 현재 사용자의 권한이 ROLE_USER이고 다음 API를 요청한다고 가정한다.

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public String admin() {
    return "admin";
}

 

AuthorizationFilter는 현재 Authentication의 권한을 확인한 뒤 접근 가능 여부를 결정한다.

권한이 없다면 AccessDeniedException을 발생시킨다.

반면 인증 정보 자체가 없다면 AuthenticationException 계열 예외가 발생한다.

 

 

Spring Security에는 인증 및 권한 관련 예외를 처리하는 전용 Filter가 존재한다.

바로 ExceptionTranslationFilter이다.

이 Filter는 직접 인증을 수행하지 않는다.

역할은 매우 단순하다.

  • AuthenticationException 처리
  • AccessDeniedException 처리

즉, Security 계층에서 발생한 예외를 적절한 HTTP 응답으로 변환하는 역할을 담당한다.

 

 

1. AuthenticationEntryPoint

인증되지 않은 사용자가 보호된 리소스에 접근하면 Spring Security는 AuthenticationEntryPoint를 호출한다.

대표적인 상황은 다음과 같다.

  • JWT 없음
  • JWT 만료
  • JWT 위변조
  • Authentication 생성 실패

기본적으로는 401 응답을 반환한다.

 

 

AuthenticationEntryPoint 구현하면 다음과 같다.

@Component
public class JwtAuthenticationEntryPoint
        implements AuthenticationEntryPoint {

    @Override
    public void commence(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException exception
    ) throws IOException {

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");

        response.getWriter().write(
            """
                {
                  "code": "AUTH_UNAUTHORIZED",
                  "message": "인증이 필요합니다."
                }
            """
        );
    }
}

 

 

 

2. AccessDeniedHandler

사용자는 인증되었지만 권한이 부족한 경우 호출된다.

대표적인 상황은 다음과 같다.

  • ROLE_USER가 관리자 API 접근
  • OWNER가 MASTER API 접근

이 경우 Spring Security는 AccessDeniedHandler를 호출한다.

 

 

AccessDeniedHandler 구현하면 다음과 같다.

@Component
public class JwtAccessDeniedHandler
        implements AccessDeniedHandler {

    @Override
    public void handle(
            HttpServletRequest request,
            HttpServletResponse response,
            AccessDeniedException exception
    ) throws IOException {

        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json;charset=UTF-8");

        response.getWriter().write(
            """
                {
                  "code":"ACCESS_DENIED",
                  "message":"접근 권한이 없습니다."
                }
            """
        );
    }
}

 

 

 

 

구현한 EntryPoint와 Handler는 exceptionHandling()을 통해 등록한다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationEntryPoint authenticationEntryPoint;
    private final JwtAccessDeniedHandler accessDeniedHandler;

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http)
            throws Exception {

        http
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(authenticationEntryPoint)
                        .accessDeniedHandler(accessDeniedHandler)
                );

        return http.build();
    }
}

 

 

 

401과 403은 어떻게 결정될까?

Spring Security는 현재 Authentication 존재 여부를 기준으로 판단한다.

Authentication 권한 결과
없음 - 401
있음 부족 403
있음 충분 200

 

즉,

401은 인증 실패

403은 권한 부족

이라는 의미이다.

 

 

실무에서 자주 하는 실수에는 무엇이 있을까?

1. AuthenticationEntryPoint에서 권한 예외까지 처리하려는 경우

AuthenticationEntryPoint는 인증 실패만 처리한다.

권한 부족은 AccessDeniedHandler가 담당한다.

두 역할을 혼동하면 올바른 상태 코드를 반환하지 못한다.

 

2. ControllerAdvice에서 처리하려는 경우

다음과 같은 코드는 Security 예외를 처리하지 못한다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<Void> handle() {
        ...
    }
}

인증과 권한 예외는 Controller에 도달하기 전에 Filter Chain에서 발생하므로 @RestControllerAdvice의 처리 대상이 아니다.

 

3. JWT 검증 실패 시 RuntimeException을 던지는 경우

JWT 검증 실패는 AuthenticationException 흐름으로 처리하는 것이 바람직하다.

단순한 RuntimeException을 던지면 ExceptionTranslationFilter가 처리하지 못해 의도한 응답을 반환하지 못할 수 있다.

 

4. 모든 인증 실패를 403으로 반환하는 경우

401과 403은 의미가 명확하게 다르다.

  • 인증되지 않음 → 401
  • 인증되었지만 권한 없음 → 403

이 둘을 구분해야 클라이언트도 적절한 후속 동작(로그인, 재인증, 권한 요청 등)을 수행할 수 있다.

 

 

전체 예외 처리 흐름을 정리해보면 다음과 같다.

Request
  ↓
JwtAuthenticationFilter
  ↓
Authentication
  ↓
AuthorizationFilter
  ↓
예외 발생
  ↓
ExceptionTranslationFilter
  ├── AuthenticationEntryPoint → 401
  └── AccessDeniedHandler → 403

 

 

 

내용을 정리해보자.

 

Spring Security는 인증 실패와 권한 부족을 서로 다른 예외로 구분하여 처리한다.

인증 정보가 없거나 유효하지 않은 경우에는 AuthenticationException이 발생하고 AuthenticationEntryPoint가 이를 처리하여 401 응답을 반환한다. 반면 인증은 완료되었지만 필요한 권한이 없는 경우에는 AccessDeniedException이 발생하며 AccessDeniedHandler가 이를 처리하여 403 응답을 반환한다.

 

이처럼 ExceptionTranslationFilter를 중심으로 인증과 인가 예외가 분리되어 처리되기 때문에, 클라이언트는 인증이 필요한 상황과 권한이 부족한 상황을 명확하게 구분할 수 있으며, 개발자는 일관된 예외 응답 형식을 제공할 수 있다.

 

 

📚  한줄 정리

AuthenticationEntryPoint는 인증 실패(401)를, AccessDeniedHandler는 권한 부족(403)을 처리하며, ExceptionTranslactionFilter는 이 두 예외를 적절한 HTTP 응답으로 변환하는 핵심 컴포넌트이다.

'Bootcamp > Implementation' 카테고리의 다른 글

Refresh Token  (0) 2026.07.15
CORS와 CSRF  (0) 2026.07.14
AuthenticationManager와 AuthenticationProvider  (0) 2026.07.10
UserDetails와 UserDetailsService  (0) 2026.07.09
Authentication과 SecurityContextHolder  (0) 2026.07.08