마이크로서비스 아키텍처(MSA) 환경에서 서비스가 수십 개로 분할되면, 각 서비스가 개별적으로 JWT 토큰을 검증하고 트래픽 제한(Rate Limiting) 로직을 구현해야 하는 문제에 직면한다. 이는 심각한 코드 중복을 유발하고 시스템 전체의 보안 정책 일관성을 무너뜨린다.
API 게이트웨이(API Gateway) 패턴: 모든 외부 클라이언트 요청이 통과하는 단일 진입점(Single Entry Point)이다. 인증, 인가, 라우팅, 속도 제한과 같은 공통 관심사를 중앙에서 처리하여 백엔드 서비스들이 순수 비즈니스 로직에만 집중할 수 있도록 분리하는 핵심 인프라 구성 요소이다.
1. 중앙 집중형 트래픽 제어의 필요성
💡 개념 비유: API 게이트웨이의 역할은 대형 공항의 '중앙 출입국 심사대'와 동일하다. 수십 개의 개별 탑승구(마이크로서비스)에서 승객의 여권을 일일이 검사하고 수하물 무게를 측정하면 심각한 병목이 발생한다. 대신 중앙 심사대에서 신원을 인증하고 규정을 위반한 수하물을 차단한 뒤, 검증이 끝난 승객만 내부 면세 구역으로 들여보내는 방식이다.
2026년 현재 MSA 환경의 API 게이트웨이 표준은 고성능 Nginx 기반의 Kong Gateway(최신 안정화 버전: 3.13)와 Java 생태계에 최적화된 Spring Cloud Gateway(최신 안정화 버전: 5.0.x)로 양분된다. 과거 널리 사용되던 Netflix Zuul은 동기식(Synchronous) I/O 모델의 한계로 인해 공식적으로 지원이 종료(Deprecated)되었다. 현대적인 게이트웨이 시스템은 모두 비동기 논블로킹(Non-blocking) 아키텍처를 채택하여 대규모 트래픽을 효율적으로 처리한다.
2. 프로덕션 레벨 구현 코드 및 해결책
Spring Cloud Gateway를 활용하여 외부 요청에 대한 JWT 인증을 수행하고, 검증된 사용자 ID를 HTTP 헤더에 담아 백엔드로 라우팅하는 커스텀 글로벌 필터 구현 방식이다. Redis 기반의 RequestRateLimiter를 조합하면 특정 사용자의 API 남용(Abuse)을 효과적으로 차단할 수 있다.
@Component
public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory<JwtAuthenticationFilter.Config> {
public JwtAuthenticationFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// Authorization 헤더 존재 여부 확인
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
String token = request.getHeaders().getOrEmpty(HttpHeaders.AUTHORIZATION).get(0).replace("Bearer ", "");
// JWT 토큰 서명 및 만료 검증 로직 구현
if (!TokenValidator.isValid(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 검증 완료 후 payload에서 추출한 User ID를 헤더에 주입 (Header Injection)
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
.header("X-User-Id", TokenValidator.extractUserId(token))
.build();
// 백엔드 마이크로서비스로 요청 전달
return chain.filter(exchange.mutate().request(modifiedRequest).build());
};
}
public static class Config {
// 라우팅별 커스텀 설정이 필요한 경우 속성 추가
}
}
⚠️ 주의사항 (Pitfalls): API 게이트웨이 내부 필터에서 데이터베이스를 직접 조회하는 무거운 동기(Synchronous) 작업을 수행하면 워커 스레드 풀이 급격히 고갈되어 전체 시스템이 마비되는 단일 장애점(SPOF)이 될 수 있다. 게이트웨이 필터 로직은 반드시 가벼운 CPU 바운드 작업(예: 메모리 상의 JWT 서명 검증)으로 제한하고, 상태가 필요한 캐싱 작업은 Redis와 같은 In-memory DB를 비동기로 호출해야 한다.
Frequently Asked Questions
Q. Netflix Zuul은 왜 Deprecated 되었으며 대안은 무엇인가?
A. Zuul 1.x는 톰캣 기반의 블로킹 I/O 처리 방식을 사용하여 트래픽이 몰릴 때마다 스레드를 추가로 생성해야 했다. 이는 곧 메모리 고갈과 응답 지연으로 이어졌다. 이 문제를 해결하기 위해 Spring 진영은 Project Reactor와 Netty를 기반으로 한 완전한 비동기식 대안인 Spring Cloud Gateway를 출시했으며, 현재 Java 생태계의 표준으로 자리 잡았다.
Q. 마이크로서비스 간의 내부 통신(East-West)에도 API Gateway를 거쳐야 하는가?
A. 성능과 네트워크 홉(Hop)을 고려할 때 내부 통신은 API Gateway를 거치지 않는 것이 원칙이다. 외부 클라이언트로부터 들어오는 North-South 트래픽만 게이트웨이가 전담하며, 클러스터 내부의 서비스 간 호출은 Service Discovery(Eureka, Consul)나 Service Mesh(Istio)를 활용하여 직접 통신하는 아키텍처를 권장한다.
Q. 게이트웨이에서 인증을 마친 사용자 정보는 백엔드로 어떻게 안전하게 전달하는가?
A. 게이트웨이에서 JWT 서명 유효성을 검증한 후, 해당 토큰에 포함된 식별자(User ID, Role 등)를 추출하여 HTTP 요청 헤더에 삽입(Header Injection)하는 패턴을 사용한다. 이렇게 하면 내부 백엔드 서비스들은 복잡한 JWT 검증 로직을 구현할 필요 없이 내부망을 통해 전달된 헤더 값을 신뢰하고 즉각적인 비즈니스 로직을 수행할 수 있다.
Post a Comment