Spring Security JWT: CustomAuthenticationEntryPoint 컴파일 에러 원인 분석 + EntryPoint/Filter 예외 처리 + ObjectMapper(Jackson 3) 정리

오병택·2026년 2월 6일
post-thumbnail

1) 문제 상황: CI에서 CustomAuthenticationEntryPoint를 못 찾아서 빌드 실패

CustomAuthenticationEntryPoint가 없어서 컴파일 실패”라고 나왔고, 실제 GitHub Actions 로그도 아래처럼 터짐.

  • SecurityConfig.java에서 CustomAuthenticationEntryPoint 타입을 못 찾음

  • JwtFilter.java import 자체가 깨짐

핵심 에러 형태

cannot find symbol: class CustomAuthenticationEntryPoint

package ... does not exist

이건 “빈 주입” 문제가 아니라 자바 컴파일 단계에서 클래스가 클래스패스에 없다고 판단한 상태다.

“로컬에는 클래스가 있는데 왜 CI에서 없지?” 대표 원인

이런 케이스가 거의 99%(푸시를 안했네요;;)

  • 커밋/푸시 누락

  • 로컬에만 있고 PR/브랜치에 안 올라가면 CI는 당연히 못 봄

  • src/test/java에 존재

JwtFilter, SecurityConfig는 src/main/java라서 compileJava는 test 소스를 안 봄
→ “존재하지만 main 컴파일 단계에서는 없음”이 됨

  • package 선언/철자/대소문자 불일치

import는 com.example.deliveryplatform.common.security.CustomAuthenticationEntryPoint인데

  • 실제 파일 상단 package가 다르거나 클래스명이 미묘하게 다르면 못 찾음

빠른 재현/확인 명령

./gradlew clean compileJava
find src -name "CustomAuthenticationEntryPoint.java" -print
git ls-files | grep CustomAuthenticationEntryPoint

2) JwtFilter에서 catch(Exception) 후 “throw는 뭘 던져야 하나?”

doFilterInternal()throws ServletException, IOException라서,
잡은 예외를 그대로 전파하되, 타입을 맞춰 주는 패턴이 안전하다.

추천 패턴

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {

    try {
        String token = tokenExtractor.extract(request);

        if (StringUtils.hasText(token)) {
            Authentication authentication = tokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);

    } catch (AuthenticationException e) {
        SecurityContextHolder.clearContext();
        authenticationEntryPoint.commence(request, response, e);
        return;

    } catch (Exception e) {
        log.error("예상치 못한 에러",e);

        if (e instanceof ServletException se) throw se;
        if (e instanceof IOException ioe) throw ioe;
        throw new ServletException(e);
    }
}

이렇게 하면:

  • 인증 관련은 401로 EntryPoint 처리

  • 예상치 못한 예외는 500 흐름으로 정상 전파(전역 예외 처리에서 잡거나 기본 에러로 처리)

3) ObjectMapper: com.fasterxml...가 없고 tools.jackson...만 보이는데?

상황설명

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

		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
		response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

		ErrorCode errorCode = (authException instanceof JwtAuthenticationException) ?
			((JwtAuthenticationException)authException).getErrorCode() :
			ErrorCode.AUTHENTICATION_REQUIRED;

		ErrorResponse errorResponse = ErrorResponse.from(errorCode);

		response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
	}

objectMapper를 사용하려는데 tools.jackson을 사용하지말고 보통 SpringBoot가 기본으로 사용하는 com.fasterxml.jackson.databind.ObjectMapper를 사용한다고 하여 사용하려 했더니 발생한 상황이다.

처음엔 “보통은 com.fasterxml.jackson.databind.ObjectMapper가 맞다”는 흐름이었는데,
dependencyInsight를 찍어보니 실제로 프로젝트는 Jackson이 이렇게 들어와 있었음:

tools.jackson.core:jackson-databind:3.0.3

spring-boot-starter-web:4.0.1 → spring-boot-starter-jackson → spring-boot-jackson → tools.jackson...

즉, 현재 환경에서는 tools.jackson.databind.ObjectMapper가 잡히는 게 자연스러운 상태.

dependencyInsight 출력에서 봐야 할 포인트:

  • tools.jackson.core:jackson-databind:3.0.3compileClasspath에 존재

  • spring-boot-starter-web:4.0.1 경로로 유입됨

  • Selected by rule / By constraint는 BOM/제약으로 버전이 3.0.3으로 정렬되었다는 신호

  • (*) repeated occurrences는 동일 트리가 여러 경로로 들어왔지만 Gradle이 한 번만 확장 표시한다는 뜻

-> Spring Framework 7 / Spring Boot 4 계열부터는 Jackson 3이 기본이고, Jackson 3의 databind/core 패키지가 tools.jackson으로 바뀐 게 공식 변경사항

EntryPoint에서 ObjectMapper 사용 방법(권장)

중요 포인트 2가지

  • new ObjectMapper()를 매번 만드는 것보다

  • 스프링이 제공하는 ObjectMapper 빈을 주입받아 쓰는 게 정석

(Spring 4.0 프로젝트 기준 import는 tools.jackson...)

4) EntryPoint에서 ErrorCode 매핑

삼항 연산자로

  • JwtAuthenticationException이면 그 안의 ErrorCode 사용

  • 아니면 기본값으로 AUTHENTICATION_REQUIRED

정리된 형태

ErrorCode errorCode = (authException instanceof JwtAuthenticationException)
    ? ((JwtAuthenticationException) authException).getErrorCode()
    : ErrorCode.AUTHENTICATION_REQUIRED;

오늘은 공부를 평소처럼 더 하려고 했는데 어제 가족들이 여행간다고 밤새 신나있어서 잠을 제대로 못 자고 하다보니 이해가 너무 안 돼서 조금만 하고 공고 추려서 이력서 넣는 날로 바꿨습니다😭

profile
걱정하지 말고 일단 해봐!

0개의 댓글