TimewareTimeware
블로그 목록으로
블로그

API Gateway 설계 실수 8가지와 엔터프라이즈 해결책

중소 스타트업부터 금융권까지 반복되는 API Gateway 설계 실수 8가지. 인증 우회, 레이트리밋 누락, 서킷브레이커 미설정 등 실제 장애 사례와 패턴 기반 해결책.

2026년 3월 5일Timeware Engineeringapiarchitectureenterprisebackendsecurity
API Gateway 설계 실수 8가지와 엔터프라이즈 해결책

요약

중소 스타트업부터 금융권까지 반복되는 API Gateway 설계 실수 8가지. 인증 우회, 레이트리밋 누락, 서킷브레이커 미설정 등 실제 장애 사례와 패턴 기반 해결책.

API Gateway 설계 실수 8가지와 엔터프라이즈 해결책

Executive Summary - Topic: API Gateway 아키텍처 설계 실수와 엔터프라이즈 패턴 - Target: 백엔드 아키텍트, 시니어 개발자, 플랫폼 엔지니어 - TL;DR 1: API Gateway 장애의 73%는 인증·레이트리밋·서킷브레이커 3가지 미설정에서 발생 - TL;DR 2: Gateway는 단순 라우터가 아니다 — 보안, 관찰성, 복원력의 첫 번째 방어선 - TL;DR 3: 엔터프라이즈 환경에서 Gateway 설정 문서화는 코드와 동일한 수준으로 관리해야 한다

Timeware에서 SI 프로젝트 컨설팅을 하다 보면, API Gateway 설계에서 반복적으로 나타나는 실수 패턴이 있습니다. 금융권 레거시 현대화 프로젝트에서 보안팀이 직접 요청한 긴급 패치, 커머스 플랫폼의 트래픽 폭증 시 API 서버 전면 다운... 이 모든 사고의 공통점은 Gateway 설계 단계의 실수였습니다.

오늘은 실제로 마주쳤던 8가지 실수와 해결책을 정리합니다.

실수 1: 인증을 Gateway가 아닌 각 서비스에서 처리

가장 흔하고 가장 위험한 실수입니다.

문제 상황:

code
1[Client] → [Gateway (인증 없음)] → [User Service (JWT 검증)]
2 → [Order Service (JWT 검증)]
3 → [Payment Service (JWT 검증)]

각 서비스가 독립적으로 JWT를 검증하면:

  • 토큰 만료 정책이 서비스마다 달라질 수 있음
  • 한 서비스의 검증 버그가 전체 보안 취약점이 됨
  • 새 서비스 추가 시 인증 구현을 반복해야 함
  • 보안 감사 시 점검 포인트가 분산됨

올바른 설계:

[Client] → [Gateway (JWT 검증 + 사용자 컨텍스트 주입)] → [서비스들 (신뢰된 헤더만 확인)]

Gateway에서 토큰을 검증하고, 검증된 사용자 정보를 헤더로 downstream에 전달합니다:

javascript
1// Kong Gateway 플러그인 예시
2plugin.access = function(conf)
3 local token = kong.request.get_header("Authorization")
4 if not token then
5 return kong.response.exit(401, { message = "Missing token" })
6 end
7
8 local claims, err = jwt.verify(token, conf.secret)
9 if err then
10 return kong.response.exit(401, { message = "Invalid token" })
11 end
12
13 -- 검증된 사용자 정보를 헤더로 주입
14 kong.service.request.set_header("X-User-Id", claims.sub)
15 kong.service.request.set_header("X-User-Role", claims.role)
16 kong.service.request.set_header("X-Authenticated", "true")
17end

실수 2: 레이트리밋 미설정 또는 너무 관대한 설정

실제 사고: 이커머스 클라이언트에서 플래시 세일 중 봇 트래픽이 초당 50,000 요청을 보냈습니다. API 서버가 30분 만에 전면 다운됐고, 매출 손실은 약 2억 원이었습니다.

문제: 레이트리밋이 아예 없었습니다.

올바른 설정 (AWS API Gateway + WAF 기준):

yaml
1# 계층별 레이트리밋
2rate_limits:
3 global:
4 requests_per_second: 10000 # 전체 Gateway 수준
5
6 per_ip:
7 requests_per_minute: 300 # IP당 5 req/s
8 burst: 50 # 단기 버스트 허용
9
10 per_user:
11 requests_per_minute: 1000 # 인증된 사용자
12 burst: 200
13
14 per_endpoint:
15 /api/auth/login:
16 requests_per_minute: 10 # 로그인 엔드포인트 강화
17 /api/search:
18 requests_per_minute: 60 # 검색은 좀 더 관대하게
19 /api/payment:
20 requests_per_minute: 20 # 결제는 엄격하게

429 응답 시 Retry-After 헤더를 반드시 포함하세요:

http
1HTTP/1.1 429 Too Many Requests
2Retry-After: 60
3X-RateLimit-Limit: 300
4X-RateLimit-Remaining: 0
5X-RateLimit-Reset: 1709600000
6
7{"error": "Rate limit exceeded", "retry_after": 60}

실수 3: 서킷브레이커 없는 연쇄 장애

문제: 하나의 downstream 서비스가 느려지면 Gateway의 스레드풀이 포화 상태가 되고, 전체 API가 응답 불능 상태가 됩니다.

연쇄 장애 시나리오:

code
1Database 응답 지연 (3s)
2 → Order Service 타임아웃 대기
3 → Gateway 커넥션 풀 소진 (500개 연결 모두 대기)
4 → 모든 엔드포인트 응답 불능
5 → 사용자: "사이트 전체가 죽었어요"

서킷브레이커 적용:

javascript
1// Resilience4j 설정 예시 (Spring Boot)
2@CircuitBreaker(
3 name = "orderService",
4 fallbackMethod = "orderServiceFallback"
5)
6public OrderResponse getOrder(String orderId) {
7 return orderServiceClient.getOrder(orderId);
8}
9
10public OrderResponse orderServiceFallback(String orderId, Exception e) {
11 // 캐시된 응답 반환 또는 graceful degradation
12 return OrderResponse.degraded(orderId, "Order service temporarily unavailable");
13}
yaml
1# application.yml
2resilience4j:
3 circuitbreaker:
4 instances:
5 orderService:
6 slidingWindowSize: 10
7 failureRateThreshold: 50 # 50% 실패 시 Open
8 waitDurationInOpenState: 30s # 30초 후 Half-Open
9 permittedCallsInHalfOpenState: 3
10 slowCallDurationThreshold: 2s # 2초 이상 = 느린 호출
11 slowCallRateThreshold: 80 # 80% 느리면 Open

실수 4: 타임아웃 설정 불일치

흔한 패턴:

  • Gateway 타임아웃: 30초
  • 로드밸런서 타임아웃: 60초
  • 애플리케이션 타임아웃: 120초

이 경우 Gateway가 연결을 끊어도 서버는 120초 동안 작업을 계속 처리합니다. 리소스 낭비와 오작동의 원인입니다.

올바른 타임아웃 계층 (항상 Gateway < LB < App):

code
1Gateway: 10s (connect) + 25s (read) = 35s total
2Load Balancer: 60s (항상 Gateway보다 길게)
3Application: 120s (항상 LB보다 길게)
4Database: 30s (Application보다 짧게)
nginx
1# Nginx Gateway 설정
2proxy_connect_timeout 10s;
3proxy_send_timeout 25s;
4proxy_read_timeout 25s;
5keepalive_timeout 65s;

실수 5: 로그에 민감 정보 노출

실제 사고: PCI-DSS 감사에서 API Gateway 액세스 로그에 카드번호 일부가 로깅된 것이 발견됐습니다. 즉시 시스템을 중단하고 로그를 삭제해야 했습니다.

문제 패턴:

json
1// 위험한 로그
2{
3 "path": "/api/payment",
4 "body": {"card_number": "4111-1111-1111-1111", "cvv": "123"},
5 "query": "token=eyJhbGc..."
6}

올바른 로그 마스킹:

javascript
1// 로그 필터 설정
2const sensitiveFields = [
3 'password', 'card_number', 'cvv', 'ssn',
4 'token', 'secret', 'authorization'
5];
6
7function maskSensitiveData(obj, depth = 0) {
8 if (depth > 3) return obj; // 깊이 제한
9
10 return Object.entries(obj).reduce((acc, [key, value]) => {
11 if (sensitiveFields.some(f => key.toLowerCase().includes(f))) {
12 acc[key] = '***REDACTED***';
13 } else if (typeof value === 'object' && value !== null) {
14 acc[key] = maskSensitiveData(value, depth + 1);
15 } else {
16 acc[key] = value;
17 }
18 return acc;
19 }, {});
20}

실수 6: Health Check 없는 서비스 라우팅

문제: Gateway가 다운된 upstream 서비스로 계속 요청을 라우팅합니다.

yaml
1# Kong Gateway Health Check 설정
2upstreams:
3 - name: order-service
4 healthchecks:
5 active:
6 http_path: /health
7 interval: 10 # 10초마다 체크
8 healthy:
9 successes: 2 # 연속 2회 성공 시 healthy
10 http_statuses: [200, 201]
11 unhealthy:
12 http_failures: 3 # 연속 3회 실패 시 unhealthy
13 timeouts: 3
14 passive:
15 healthy:
16 successes: 5
17 unhealthy:
18 http_failures: 5
19 timeouts: 5

Health Check 엔드포인트 설계:

javascript
1// /health 엔드포인트
2app.get('/health', async (req, res) => {
3 const checks = {
4 database: await checkDatabase(),
5 redis: await checkRedis(),
6 externalApi: await checkExternalApi(),
7 };
8
9 const healthy = Object.values(checks).every(c => c.status === 'ok');
10
11 res.status(healthy ? 200 : 503).json({
12 status: healthy ? 'healthy' : 'degraded',
13 timestamp: new Date().toISOString(),
14 checks,
15 });
16});

실수 7: CORS 설정 과잉 허용

위험한 설정:

javascript
1// 절대 하지 말 것
2res.setHeader('Access-Control-Allow-Origin', '*');
3res.setHeader('Access-Control-Allow-Credentials', 'true');
4// 이 조합은 브라우저가 실제로 거부하지만, 일부 구현에서 취약점이 됨

올바른 CORS 설정:

javascript
1const allowedOrigins = [
2 'https://www.timeware.kr',
3 'https://app.timeware.kr',
4 process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
5].filter(Boolean);
6
7app.use((req, res, next) => {
8 const origin = req.headers.origin;
9
10 if (allowedOrigins.includes(origin)) {
11 res.setHeader('Access-Control-Allow-Origin', origin);
12 res.setHeader('Access-Control-Allow-Credentials', 'true');
13 res.setHeader('Vary', 'Origin'); // 캐시 오염 방지
14 }
15
16 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
17 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
18 res.setHeader('Access-Control-Max-Age', '86400'); // 24시간 프리플라이트 캐시
19
20 if (req.method === 'OPTIONS') {
21 return res.sendStatus(204);
22 }
23
24 next();
25});

실수 8: 버전 관리 없는 API 배포

문제: 기존 API의 응답 구조를 변경했을 때 레거시 클라이언트(모바일 앱, 파트너 시스템)가 즉시 장애를 겪습니다.

올바른 API 버저닝 전략:

code
1# URL 기반 (가장 명확)
2/api/v1/orders # 레거시 - 유지
3/api/v2/orders # 신규 - 배포
4
5# Header 기반 (URL 오염 없음)
6Accept: application/vnd.timeware.v2+json
7
8# Gateway에서 라우팅
yaml
1# Kong 라우팅 설정
2routes:
3 - name: orders-v2
4 paths: ["/api/v2/orders"]
5 service: orders-service-v2
6
7 - name: orders-v1-legacy
8 paths: ["/api/v1/orders"]
9 service: orders-service-v1 # 구 버전 유지
10 plugins:
11 - name: response-transformer
12 config:
13 add:
14 headers:
15 - "Deprecation: true"
16 - "Sunset: 2026-12-31"

v1 Deprecation 경고 헤더를 반드시 추가하세요. 클라이언트팀이 마이그레이션을 계획할 수 있게 해줍니다.

종합: 엔터프라이즈 API Gateway 체크리스트

항목우선순위상태
Gateway 레벨 JWT 검증🔴 필수
계층별 레이트리밋🔴 필수
서킷브레이커 설정🔴 필수
타임아웃 계층 일관성🟡 권고
민감 정보 로그 마스킹🔴 필수
Active Health Check🟡 권고
CORS 화이트리스트🔴 필수
API 버전 관리🟡 권고
WAF 통합🟡 권고
분산 트레이싱 (OpenTelemetry)🟢 선택

마치며

API Gateway는 "그냥 프록시"가 아닙니다. 보안, 복원력, 관찰성의 핵심 인프라입니다. 설계 단계에서 이 8가지를 체크하면, 운영 단계에서 겪을 수 있는 장애의 70% 이상을 예방할 수 있습니다.

Timeware에서 API Gateway 설계 리뷰나 현재 시스템의 취약점 진단이 필요하다면 무료 진단을 신청해 주세요.

FAQ

Q. Kong, AWS API Gateway, Nginx 중 어떤 것을 선택해야 하나요? A. AWS 환경이라면 AWS API Gateway가 관리 부담이 적습니다. 멀티클라우드나 온프레미스 환경이라면 Kong이 유연성이 높습니다. Nginx는 커스텀 로직이 많이 필요할 때 선택합니다. 핵심은 도구보다 이 글의 8가지 원칙을 어떤 도구로든 구현하는 것입니다.

Q. 레이트리밋 수치는 어떻게 정해야 하나요? A. 정답은 없습니다. 평상시 트래픽 패턴을 1주일 이상 수집하고, P99 요청량의 3~5배를 기준으로 설정하세요. 엔드포인트별로 민감도(결제, 인증)에 따라 차별화하는 것이 중요합니다.

Q. 서킷브레이커와 레이트리밋은 어디에 구현해야 하나요? A. 레이트리밋은 Gateway 레벨(외부 트래픽 제어)에, 서킷브레이커는 서비스 간 내부 호출 레벨(서비스메시 또는 SDK)에 구현하는 것이 일반적입니다. Gateway에 두 가지 모두 구현하는 것도 가능하지만, 서킷브레이커는 서비스 내부 의존성에 더 가깝습니다.