Chef.Yeon
Code Cook
Chef.Yeon
전체 방문자
오늘
어제
  • 분류 전체보기 (230)
    • 게임 개발 (1)
      • Unity (1)
    • Android (27)
      • Kotlin (19)
      • 우아한테크코스 5기 (4)
    • Language (11)
      • 파이썬 (3)
      • Java (7)
    • DB (2)
      • SQL (16)
    • Spring (25)
    • 코딩테스트 (56)
    • Git (1)
    • TIL (85)
    • DevOps (6)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 백준
  • kotlin
  • 다이나믹 프로그래밍
  • grafana
  • 안드로이드
  • SQL
  • rsocket
  • 파이썬
  • kibana
  • 코딩테스트
  • 코틀린 인 액션
  • spring
  • ec2
  • MariaDB
  • 프리코스
  • 코틀린
  • 우아한테크코스
  • til
  • 프로그래머스
  • 내림차순
  • 에라토스테네스의 체
  • Docker
  • Android
  • webflux
  • java
  • 문자열
  • 레포지토리
  • elasticsearch
  • Wil
  • enum

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Chef.Yeon

Code Cook

[TIL - 20231222] AuthenticationEntryPoint를 사용한 JWT 예외 핸들링
TIL

[TIL - 20231222] AuthenticationEntryPoint를 사용한 JWT 예외 핸들링

2023. 12. 22. 20:17

 

💻문제점

JwtAuthenticationFilter.java

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String extractToken = request.getHeader(JwtUtils.AUTH_TOKEN_HEADER);
        jwtUtils.extractBearerToken(extractToken)
                .ifPresent(
                        jwt -> {
                            jwtUtils.validateToken(jwt);
                            if (SecurityContextHolder.getContext().getAuthentication() == null) {
                                String email = getUserEmailFromJwt(jwt);
                                UserDetails userDetails =
                                        userDetailsServiceImpl.loadUserByUsername(email);
                                setAuthenticationInSecurityContext(request, userDetails);
                            }
                        });
        filterChain.doFilter(request, response);
    }

 

 

JwtUtils.java

    public void validateToken(String token) {
        try {
            extractAllClaims(token);
        } catch (SecurityException | MalformedJwtException e) {
            throw new InvalidJwtException(ErrorCode.INVALID_TOKEN_SIGNATURE);
        } catch (ExpiredJwtException e) {
            throw new InvalidJwtException(ErrorCode.EXPIRED_TOKEN);
        } catch (UnsupportedJwtException e) {
            throw new InvalidJwtException(ErrorCode.UNSUPPORTED_TOKEN);
        } catch (IllegalArgumentException e) {
            // token claim is blank or token is null
            throw new InvalidJwtException(ErrorCode.INVALID_TOKEN);
        } catch (Exception e) {
            log.warn("처리되지 않은 JWT 오류입니다.");
            throw new InvalidJwtException(ErrorCode.UNEXPECTED_TOKEN);
        }
    }

 

ErrorCode.java

@Getter
public enum ErrorCode {
    // auth
    EXPIRED_TOKEN("AUTH_001", "JWT 토큰이 만료되었습니다."),
    INVALID_TOKEN("AUTH_002", "JWT 토큰이 유효하지 않습니다."),
    INVALID_TOKEN_SIGNATURE("AUTH_003", "JWT 서명이 유효하지 않습니다."),
    UNSUPPORTED_TOKEN("AUTH_004", "지원되지 않는 JWT 토큰입니다."),
    UNEXPECTED_TOKEN("AUTH_005", "JWT 처리 중 예상치 못한 오류가 발생했습니다."),
    
    //... 다른 ErrorCode

    private final String code;
    private final String description;

    ErrorCode(String code, String description) {
        this.code = code;
        this.description = description;
    }
}

 

JWT 예외가 발생하면 InvalidJwtException이라는 커스텀 예외를 던진다.

 

하지만, 이에 대한 핸들링이 아직 되어있지 않다. 때문에 클라이언트 측에서는 JWT 오류가 발생했는지에 대해 알 수 없다.

 

ControllerAdvice는 Controller단에서만 적용되기 때문에 JwtAuthenticationFilter에서 발생하는 예외에 대해서는 핸들링되지 않는다.

 

때문에 이에 대한 핸들링을 따로 해줄 필요가 있었다.


🔍해결

AuthenticationEntryPoint를 사용하여 인증에서 발생한 에러를 처리할 수 있다.

 

ErrorResponse.java

public record ErrorResponse(String errorCode, String description) {
    public static ErrorResponse from(ErrorCode errorCode) {
        return new ErrorResponse(errorCode.getCode(), errorCode.getDescription());
    }

    public static ErrorResponse from(ErrorCode errorCode, String description) {
        return new ErrorResponse(errorCode.getCode(), description);
    }
}

 

JwtAuthenticationEntryPoint.java

@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException authException)
            throws IOException, ServletException {
        ErrorCode errorCode = (ErrorCode) request.getAttribute(JwtUtils.EXCEPTION_ATTRIBUTE);
        log.error("[Exception] - JwtAuthenticationEntryPoint.commence - uri: {} - {}", request.getRequestURI(), errorCode);
        setErrorResponse(response, errorCode);
    }

    private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode)
            throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("utf-8");
        ErrorResponse errorResponse = ErrorResponse.from(errorCode);
        String result = new ObjectMapper().writeValueAsString(errorResponse);
        response.getWriter().write(result);
    }
}

 

JwtAuthenticationFilter.java

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String extractToken = request.getHeader(JwtUtils.AUTH_TOKEN_HEADER);
        jwtUtils.extractBearerToken(extractToken)
                .ifPresent(
                        jwt -> {
                            try {
                                jwtUtils.validateToken(jwt);
                                if (SecurityContextHolder.getContext().getAuthentication()
                                        == null) {
                                    String email = getUserEmailFromJwt(jwt);
                                    UserDetails userDetails =
                                            userDetailsServiceImpl.loadUserByUsername(email);
                                    setAuthenticationInSecurityContext(request, userDetails);
                                }
                            } catch (InvalidJwtException e) {
                                request.setAttribute(JwtUtils.EXCEPTION_ATTRIBUTE, e.getErrorCode());
                            }
                        });
        filterChain.doFilter(request, response);
    }

 

SecurityConfig.java

    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
    	//...
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling(
                        config -> config.authenticationEntryPoint(jwtAuthenticationEntryPoint)) //추가
                .authorizeHttpRequests(
                        authorize ->
                                authorize
                                        .requestMatchers(
                                                new AntPathRequestMatcher(
                                                        "/api/v1/members/oauth/*/login"))
                                        .permitAll()
                                        .anyRequest()
                                        .authenticated())
                .sessionManagement(
                        session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

 

728x90

'TIL' 카테고리의 다른 글

[TIL - 20231228] Interface의 default 메서드를 활용한 Enum 확장  (0) 2023.12.28
[TIL - 20231226] jacoco 패키지, 클래스 Report에서 제외  (0) 2023.12.26
[TIL - 20231222] searchWeeklyIntakeCalories 로직 개선  (0) 2023.12.22
[TIL-20231222] Illegal pop() with non-matching JdbcValuesSourceProcessingState  (0) 2023.12.22
[TIL-20231220] 리플렉션을 사용한 private 필드 접근 및 값 설정  (0) 2023.12.20
    'TIL' 카테고리의 다른 글
    • [TIL - 20231228] Interface의 default 메서드를 활용한 Enum 확장
    • [TIL - 20231226] jacoco 패키지, 클래스 Report에서 제외
    • [TIL - 20231222] searchWeeklyIntakeCalories 로직 개선
    • [TIL-20231222] Illegal pop() with non-matching JdbcValuesSourceProcessingState
    Chef.Yeon
    Chef.Yeon
    보기 좋고 깔끔한 코드를 요리하기 위해 노력하고 있습니다.

    티스토리툴바