[공부정리] SpringCloudGateway에서WebExceptionHandler로 전역 에러 처리

jeyong·2024년 3월 9일
0

공부 / 생각 정리  

목록 보기
40/120
post-thumbnail
post-custom-banner

이번에 작성할 내용은 SpringCloudGateway에서 WebExceptionHandler를 이용하여 예외를 처리하는 방법에 대해서 정리할 것이다.

1. WebExceptionHandler

Spring Cloud Gateway는 Spring Webflux를 기반으로 하기 때문에, Reactive 프로그래밍 패러다임에 맞는 전역 에러 처리 방식을 적용해야 한다. 이와 관련된 좋은 자료로 아래 게시글을 추천한다.

Spring Boot 전역 에러 처리

평소 사용하는 @ExceptionHandler 방식이 아닌, WebExceptionHandler를 사용해보았고 해당 내용을 기술하겠다.

2. 구현 코드

주로 MessageSource를 이용한 국제화 예외처리를 하기 때문에 해당 빙식으로 구현한 코드를 기반으로 설명하겠다.

2-1. CustomWebExceptionHandler

@Component
@Order(-2)
public class CustomWebExceptionHandler implements WebExceptionHandler {

    private final ObjectMapper objectMapper;
    private final ResponseHandler responseHandler;

    public CustomWebExceptionHandler(ObjectMapper objectMapper, ResponseHandler responseHandler) {
        this.objectMapper = objectMapper;
        this.responseHandler = responseHandler;
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        HttpStatus status;
        Response response;

        if (ex instanceof ExpiredJwtException) {
            status = HttpStatus.UNAUTHORIZED;
            response = responseHandler.getFailureResponse(EXPIRED_JWT_EXCEPTION);
        } else if (ex instanceof AuthenticationException) {
            status = HttpStatus.UNAUTHORIZED;
            response = responseHandler.getFailureResponse(AUTHENTICATION_ENTRY_POINT_EXCEPTION);
        } else if (ex instanceof AccessDeniedException) {
            status = HttpStatus.FORBIDDEN;
            response = responseHandler.getFailureResponse(ACCESS_DENIED_EXCEPTION);
        } else {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
            response = responseHandler.getFailureResponse(EXCEPTION);
        }

        exchange.getResponse().setStatusCode(status);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        byte[] bytes = toJson(response);

        return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(bytes)));
    }

    private byte[] toJson(Response response) {
        try {
            return objectMapper.writeValueAsBytes(response);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Spring WebFlux의 WebExceptionHandler 인터페이스를 구현하며, 특정 예외 유형에 따라 적절한 HTTP 상태 코드와 응답 본문을 반환하는 역할을 수행한다.

  • @Order(-2) 어노테이션을 사용하여 기본 WebExceptionHandler보다 우선 순위를 높게 설정한다. 이는 Spring Framework가 에러 처리 체인에서 이 핸들러를 먼저 적용하도록 한다.
  • handle 메서드는 ServerWebExchange와 Throwable을 매개변수로 받아, 발생한 예외에 따라 적절한 응답을 생성한다.

2-2. JwtStoreAuthenticationFilter

@Component
public class JwtStoreAuthenticationFilter extends AbstractGatewayFilterFactory<JwtStoreAuthenticationFilter.Config> {

    private final TokenHandler storeAccessTokenHandler;

    public JwtStoreAuthenticationFilter(TokenHandler storeAccessTokenHandler) {
        super(Config.class);
        this.storeAccessTokenHandler = storeAccessTokenHandler;
    }

    public static class Config {

    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> extractToken(exchange)
                .map(token -> storeAccessTokenHandler.parse(token)
                        .flatMap(claims -> {
                            String storeId = claims.getId();
                            exchange.getRequest().mutate().header("Store-ID", storeId).build();
                            return chain.filter(exchange);
                        })
                        .onErrorResume(e -> {
                            if (e instanceof ExpiredJwtException) {
                                return Mono.error(new ExpiredJwtException(null, null, "Token expired"));
                            } else {
                                return Mono.error(new AuthenticationException("Invalid token"));
                            }
                        }))
                .orElseGet(() -> Mono.error(new AuthenticationException("Authorization header missing")));
    }

    private Optional<String> extractToken(ServerWebExchange exchange) {
        return Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("Authorization"))
                .filter(authHeader -> authHeader.startsWith("Bearer "));
    }
}

JwtStoreAuthenticationFilter는 JWT 토큰을 검증하고, 토큰이 유효한 경우 사용자 요청에 추가 정보를 포함시키는 역할을 한다.

  • 유효하지 않은 경우, 적절한 예외를 발생시키고 이를 CustomWebExceptionHandler를 통해 처리하도록 구현하였다.

3. Mono.error() vs Throw

SpringWebflux에서 에러를 발생시킬때는 Mono.error()를 사용한다. 하지만 Throw 사용해도 된다. 이 둘의 차이는 아래 게시글을 추천한다.

Mono.error() 와 Throw 의 차이

에러 처리는 Mono 에 담아 처리하는 것이 WebFlux 다운 구현이라고 생각된다.

profile
노를 젓다 보면 언젠가는 물이 들어오겠지.
post-custom-banner

0개의 댓글