Spring

Spring Gateway 에러 핸들링

horing 2023. 9. 19. 14:10

Spring Cloud Gateway는 @ControllerAdvice를 지원하지 않아 ErrorWebExceptionHandler를 구현해야한다.

 

ErrorExceptionConfig.java

@Configuration
@RequiredArgsConstructor
public class ErrorExceptionConfig {

    private final ObjectMapper objectMapper;
    @Bean
    public ErrorWebExceptionHandler globalExceptionHandler(){
        return new GlobalExceptionHandler(objectMapper);
    }
}

ErrorWebExceptionHandler를 bean으로 등록하면 에러핸들러를 커스텀하여 사용할 수 있다.

 

GlobalExceptionHandler.java

@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();

        if(response.isCommitted()){
            return Mono.error(ex);
        }

        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        if(ex instanceof ResponseStatusException){
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }

        CustomException customException = new CustomException(ErrorCode.INVALID_ACCESS_TOKEN);

        ErrorResponseDto errorResponseDto = new ErrorResponseDto(customException.getErrorCode().getMessage(), customException.getErrorCode()
            .getStatus());

        String error = "Gateway Error";

        try {
            error = objectMapper.writeValueAsString(errorResponseDto);
        } catch (JsonProcessingException e) {
            log.error("JsonProcessingException : " + e.getMessage());
        }

        byte[] bytes = error.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        return exchange.getResponse().writeWith(Flux.just(buffer));
    }
}
  • GlobalExceptionHandler : 게이트웨이 내부에서 발생하는 예외에 대한 전체적인 핸들링을 담당하는 객체
  • Order : 내부 bean보다 우선 순위를 높여 해당 빈이 동작하게 설정

 

AuthorizationHeaderFilter.java

@Slf4j
@Component
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> implements
    Ordered {

    ...

    @Override
    public int getOrder() {
        return -2;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)){
                throw new CustomException(INVALID_ACCESS_TOKEN);
            }

            ...

            return chain.filter(exchange);
        });
    }

        ...
}

필터에서 CustomException 적용

 

응답 예시

{
    "message": "유효하지 않은 accessToken 입니다.",
    "status": 401
}

 

에러 핸들링

GlobalExceptionHandler.java에서 생성된 CustomException의 ErrorCode값이 고정되어 AuthorizationHeaderFilter.java에서 어떠한 Exception을 던져도 해당 CustomException의 ErrorCode가 예외로 던져진다.

예외값이 고정되어 던져지므로 상황별로 예외를 구분하여 전달할 수 없음.

 

필터에서 예외처리 시 GlobalExceptionHandler.java의 handle 메소드가 실행되므로 GlobalExceptionHandler에서 예외처리 상황을 구분하여 처리함.(필터에 들어오는 ServerWebExchange 값이 그대로 GlobalExceptionHandler의 handle 메소드로 전달됨)

 

GlobalExceptionHandler.java

@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
                ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        if(response.isCommitted()){
            return Mono.error(ex);
        }

        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        if(ex instanceof ResponseStatusException){
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }

        CustomException customException = new CustomException(ErrorCode.INVALID_ACCESS_TOKEN);

                if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)){
            customException = new CustomException(ErrorCode.INVALID_ACCESS);
        }

        ErrorResponseDto errorResponseDto = new ErrorResponseDto(customException.getErrorCode().getMessage(), customException.getErrorCode()
            .getStatus());

        String error = "Gateway Error";

        try {
            error = objectMapper.writeValueAsString(errorResponseDto);
        } catch (JsonProcessingException e) {
            log.error("JsonProcessingException : " + e.getMessage());
        }

        byte[] bytes = error.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        return exchange.getResponse().writeWith(Flux.just(buffer));
    }
}