마이크로서비스 아키텍처에서 시스템은 여러 개의 독립적인 서비스로 분리됩니다. 이러한 환경에서 클라이언트가 각각의 마이크로서비스에 직접 접근해야 한다면 다음과 같은 복잡성과 문제가 발생할 수 있습니다.
- 클라이언트 애플리케이션의 복잡성 증대: 여러 서비스의 주소를 관리하고 각기 다른 방식으로 호출해야 합니다.
- 인증 및 권한 부여의 어려움: 각 서비스마다 개별적으로 인증 로직을 구현해야 하므로 관리 오버헤드가 커집니다.
- 교차 출처(CORS) 요청 처리의 복잡성: 브라우저 환경에서 발생할 수 있는 교차 출처 제한을 서비스마다 해결해야 합니다.
이러러한 도전 과제들은 API 게이트웨이를 도입함으로써 효과적으로 해결할 수 있습니다. API 게이트웨이는 시스템의 통합된 접근 지점 역할을 수행합니다. 이는 내부 서비스 구조를 클라이언트로부터 숨기고, 인증/인가, 모니터링, 요청 라우팅, 속도 제한 등 공통적이고 횡단 관심사 기능을 한 곳에서 처리할 수 있도록 돕습니다.
현재 업계에서 널리 활용되는 게이트웨이 솔루션은 다음과 같습니다.
- Nginx + Lua: Nginx의 리버스 프록시 및 로드 밸런싱 기능을 활용하며, Lua 스크립트로 간단한 비즈니스 로직을 추가할 수 있습니다. 고성능이 요구되는 환경에 적합합니다.
- Kong: Nginx와 Lua를 기반으로 개발된 오픈소스 게이트웨이로, 풍부한 플러그인(속도 제한, 인증 등)을 제공하여 즉시 사용 가능합니다. HTTP 프로토콜만 지원하며, 커스터마이징이 다소 어렵다는 단점이 있습니다.
- Netflix Zuul: Netflix에서 개발한 게이트웨이로, Java 기반이며 다양한 기능을 제공하여 커스터마이징이 용이합니다. 하지만 동적 설정 관리 및 성능 측면에서 Nginx 기반 솔루션보다 떨어지는 경향이 있습니다.
- Spring Cloud Gateway: Spring 생태계에서 Zuul의 후속으로 개발된 게이트웨이입니다. 본 문서에서는 이 솔루션을 중심으로 상세히 다룹니다.
참고: Spring Cloud Alibaba 스택은 자체적인 게이트웨이 컴포넌트를 제공하지 않으므로, Spring Cloud Gateway를 함께 사용하는 것이 일반적입니다.
Spring Cloud Gateway 개요
Spring Cloud Gateway는 Spring 5.0, Spring Boot 2.0, Project Reactor 기술을 기반으로 구축되었습니다. 이 게이트웨이는 마이크로서비스 아키텍처에서 API 라우팅을 효율적으로 관리할 수 있는 통합된 방안을 제공하며, Netflix Zuul을 대체하는 것을 목표로 합니다. 단순히 요청을 전달하는 것을 넘어, 필터 체인(Filter Chain)을 통해 보안, 모니터링, 속도 제한과 같은 게이트웨이의 핵심 기능을 지원합니다.
주요 특징 및 장점:
- 고성능: 이전 세대 게이트웨이에 비해 향상된 처리량과 낮은 지연 시간을 제공합니다.
- 강력한 기능: 요청 전달, 모니터링, 속도 제한 등 실용적인 기능이 내장되어 있습니다.
- 유연한 설계: 확장 및 커스터마이징이 용이하여 다양한 요구사항에 대응할 수 있습니다.
Spring Cloud Gateway의 핵심 구성 요소는 다음과 같습니다.
- 라우트 (Route): 게이트웨이 설정의 기본 단위입니다. 고유한 식별자(ID), 요청을 전달할 목적지 URI, 요청 매칭 조건(단언), 요청/응답 처리 로직(필터)으로 구성됩니다.
- 단언 (Predicate): Java 8의
Predicate인터페이스를 활용하여 HTTP 요청의 다양한 속성(경로, 헤더, 쿼리 파라미터 등)을 기반으로 라우팅 조건을 정의합니다. 모든 단언이 참일 경우에만 해당 라우트가 활성화됩니다. - 필터 (Filter): 게이트웨이를 통과하는 요청과 응답을 가로채어 수정하거나 추가 작업을 수행합니다. 요청 전(pre) 단계와 응답 후(post) 단계에서 모두 동작할 수 있습니다.
Spring Cloud Gateway 빠른 시작 가이드
본 섹션에서는 브라우저 요청이 API 게이트웨이를 거쳐 특정 마이크로서비스로 전달되는 과정을 단계별로 설명합니다.
1. 기본 라우팅 설정
1단계: api-gateway-app 모듈 생성 및 의존성 추가
Maven pom.xml 파일에 Spring Cloud Gateway 의존성을 추가합니다. Spring Cloud Gateway는 WebFlux 기반이므로, 일반적인 Spring Web MVC의 spring-boot-starter-web 의존성은 제외해야 합니다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>my-microservice-project</artifactId>
<groupId>com.example.system</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway-app</artifactId>
<dependencies>
<!-- Spring Cloud Gateway 코어 의존성 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
</project>
2단계: 메인 애플리케이션 클래스 정의
표준 Spring Boot 애플리케이션 시작 클래스를 작성합니다.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3단계: 설정 파일 (application.yml)에 라우팅 규칙 추가
게이트웨이의 포트와 정적 라우팅 규칙을 정의합니다. 여기서는 /public/** 경로로 들어오는 요청을 https://www.example.org로 전달하는 예시입니다.
server:
port: 8080 # 게이트웨이 서비스 포트
spring:
application:
name: api-gateway-app
cloud:
gateway:
routes: # 라우팅 규칙 배열
- id: public_web_route # 라우트의 고유 식별자
uri: https://www.example.org # 요청을 전달할 대상 URI
order: 1 # 라우트의 우선순위 (낮은 숫자일수록 높음)
predicates: # 단언: 라우팅을 위한 조건 목록
- Path=/public/** # 요청 경로가 '/public/'으로 시작하는 경우
filters: # 필터: 요청/응답을 수정하는 작업 목록
- StripPrefix=1 # 대상 서비스로 전달하기 전, 경로의 첫 번째 부분(예: '/public')을 제거
4단계: 애플리케이션 실행 및 동작 확인
게이트웨이 애플리케이션을 시작한 후, 웹 브라우저에서 http://localhost:8080/public/about와 같이 접근하면 실제로는 https://www.example.org/about로 요청이 전달되는 것을 확인할 수 있습니다.
2. 서비스 디스커버리 연동 라우팅
앞선 정적 라우팅 방식은 대상 서비스의 URI가 고정되어 있어 유연성이 떨어집니다. 이제 서비스 디스커버리(예: Nacos)를 활용하여 동적으로 서비스 주소를 조회하고 로드 밸런싱을 적용하는 방식으로 개선합니다.
1단계: Nacos 디스커버리 클라이언트 의존성 추가
<!-- Nacos 서비스 디스커버리 클라이언트 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2단계: 메인 클래스에 @EnableDiscoveryClient 애너테이션 추가
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient // 서비스 디스커버리 기능 활성화
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3단계: 설정 파일 (application.yml) 수정
Nacos 서버 주소를 추가하고, 라우트의 uri를 서비스 ID 기반의 로드 밸런싱 URI(lb://[서비스_ID])로 변경합니다.
server:
port: 8080
spring:
application:
name: api-gateway-app
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos 서버 주소
gateway:
routes:
- id: product_api_service_route
uri: lb://product-api-service # 'lb'는 로드 밸런서를 통해 Nacos에 등록된 'product-api-service'를 찾음을 의미
predicates:
- Path=/product/**
filters:
- StripPrefix=1
4단계: 테스트
이제 product-api-service라는 이름으로 Nacos에 등록된 마이크로서비스 인스턴스로 요청이 로드 밸런싱되어 전달됩니다.
3. 간결한 라우팅 (Discovery Locator)
개별 마이크로서비스마다 라우팅 규칙을 일일이 설정하는 것은 번거로울 수 있습니다. Spring Cloud Gateway는 Discovery Locator 기능을 통해 서비스 디스커버리에 등록된 서비스들을 자동으로 라우팅 규칙으로 변환할 수 있습니다.
1단계: 라우팅 규칙 제거 및 discovery.locator 활성화
server:
port: 8080
spring:
application:
name: api-gateway-app
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true # Gateway가 Nacos의 서비스를 자동으로 라우트 규칙으로 변환하도록 활성화
lower-case-service-id: true # 라우팅 경로에 서비스 ID를 소문자로 사용 (선택 사항)
Spring Cloud 2020.0 (Spring Boot 2.4+) 이상 버전에서는 로드 밸런서 구현을 위해
spring-cloud-starter-loadbalancer의존성이 필요할 수 있습니다.<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
2단계: 애플리케이션 실행 및 테스트
이 설정을 통해 게이트웨이는 http://localhost:8080/SERVICE-ID/api/path와 같은 형식으로 들어오는 요청을 해당 SERVICE-ID를 가진 마이크로서비스로 자동으로 라우팅합니다.
CORS (Cross-Origin Resource Sharing) 설정
Spring Cloud Gateway는 교차 출처 요청에 대한 유연한 설정을 제공합니다. 다음 YAML 설정을 통해 CORS 문제를 해결할 수 있습니다.
spring:
cloud:
gateway:
globalcors: # 전역 CORS 설정
corsConfigurations:
'[/**]': # 모든 경로에 대해 적용
allowedOrigins: "*" # 허용할 출처 (예: http://localhost:3000, *는 모든 출처 허용)
allowedMethods: "*" # 허용할 HTTP 메서드 (예: GET, POST, PUT, DELETE)
allowedHeaders: "*" # 허용할 요청 헤더 (예: Content-Type, Authorization)
allowCredentials: true # 자격 증명(쿠키, HTTP 인증)을 요청과 함께 보낼지 여부
maxAge: 3600 # 사전 검증(Preflight) 요청의 결과를 캐시할 최대 시간 (초)
Gateway 핵심 아키텍처
기본 개념 재확인
- 라우트 (Route): 고유 ID, 대상 URI, 단언(Predicate), 필터(Filter)로 구성된 라우팅 규칙의 기본 단위입니다.
- 단언 (Predicate): 요청의 특정 조건을 평가하여 라우트의 일치 여부를 결정하는 함수형 인터페이스입니다.
- 필터 (Filter): 요청이 대상 서비스로 전달되기 전(pre)이나 응답이 클라이언트로 돌아가기 전(post)에 요청 또는 응답을 수정하는 역할을 합니다.
요청 처리 흐름
Gateway를 통한 요청 처리 과정은 다음과 같습니다.
- 클라이언트가 Gateway 서버로 HTTP 요청을 전송합니다.
- 요청은
HttpWebHandlerAdapter에 의해 Gateway 컨텍스트로 변환됩니다. - Gateway 컨텍스트는
DispatcherHandler로 전달되며, 이는RoutePredicateHandlerMapping에 요청을 위임합니다. RoutePredicateHandlerMapping은 등록된 라우트 정의를 탐색하고, 각 라우트의 단언(Predicate)을 평가하여 요청에 일치하는 라우트를 찾습니다.- 일치하는 라우트를 찾으면,
FilteringWebHandler가 해당 라우트에 정의된 필터들과 전역 필터들을 포함하는 필터 체인을 구성합니다. - 요청은 필터 체인(Pre-Filter -> 마이크로서비스 -> Post-Filter)을 순차적으로 통과하며 처리된 후 최종 응답이 클라이언트로 반환됩니다.
단언 (Predicates)
단언은 "어떤 조건에서 라우트가 활성화될 것인가"를 정의하는 데 사용됩니다. Spring Cloud Gateway는 다양한 내장 단언 팩토리를 제공하며, 사용자 정의 단언도 구현할 수 있습니다.
내장 라우트 단언 팩토리
Spring Cloud Gateway는 HTTP 요청의 다양한 속성과 일치시킬 수 있는 여러 내장 단언 팩토리를 제공합니다.
- 시간 기반 단언:
After,Before,Between(특정 시간 전/후/사이 요청 허용)- 예:
- After=2023-10-27T10:00:00+09:00[Asia/Seoul]
- 예:
- 원격 주소 기반 단언:
RemoteAddr(특정 IP 주소 범위 내 요청만 허용)- 예:
- RemoteAddr=192.168.1.0/24
- 예:
- 쿠키 기반 단언:
Cookie(특정 이름과 정규식에 일치하는 쿠키 값 확인)- 예:
- Cookie=session_id, [a-zA-Z0-9]+
- 예:
- 헤더 기반 단언:
Header(특정 이름과 정규식에 일치하는 헤더 값 확인)- 예:
- Header=X-Auth-Token, .+
- 예:
- 호스트 기반 단언:
Host(요청의 Host 헤더가 패턴에 일치하는지 확인)- 예:
- Host=*.example.com
- 예:
- HTTP 메서드 기반 단언:
Method(특정 HTTP 메서드(GET, POST 등)만 허용)- 예:
- Method=GET,POST
- 예:
- 경로 기반 단언:
Path(요청 URI 경로가 패턴에 일치하는지 확인)- 예:
- Path=/users/{userId}
- 예:
- 쿼리 파라미터 기반 단언:
Query(특정 이름과 정규식에 일치하는 쿼리 파라미터 값 확인)- 예:
- Query=status, (active|inactive)
- 예:
- 가중치 기반 단언:
Weight(동일 그룹 내 라우트들 간에 가중치에 따라 요청을 분배)- 예시:
routes: - id: service_version_a uri: lb://my-service-v1 predicates: - Path=/api/data/** - Weight=api-traffic-split, 8 # 80% 트래픽 - id: service_version_b uri: lb://my-service-v2 predicates: - Path=/api/data/** - Weight=api-traffic-split, 2 # 20% 트래픽
내장 단언 사용 예시:
server: port: 8080 spring: application: name: api-gateway-app cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: discovery: locator: enabled: true routes: - id: restricted_product_route uri: lb://product-api-service predicates: - Path=/product-api/admin/** # '/product-api/admin/' 경로 요청 - Method=POST # HTTP POST 메서드만 허용 - Query=apiKey, secret_key_.* # 'apiKey' 쿼리 파라미터가 'secret_key_'로 시작하는 경우 filters: - StripPrefix=1사용자 정의 라우트 단언 팩토리
특정 비즈니스 로직에 기반한 단언이 필요한 경우, 사용자 정의 단언 팩토리를 구현할 수 있습니다. 예를 들어, 요청 파라미터의
age값이 특정 범위 내에 있을 때만 요청을 허용하는 단언을 만들어 봅시다.1단계: 설정 파일에
AgeRange단언 추가server: port: 8080 spring: application: name: api-gateway-app cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: discovery: locator: enabled: true routes: - id: age_restricted_access uri: lb://member-service predicates: - Path=/member/** - AgeRange=18,65 # age 파라미터가 18세 이상 65세 이하인 경우 허용 filters: - StripPrefix=12단계: 사용자 정의 단언 팩토리 클래스 구현
단언 팩토리 클래스는
AbstractRoutePredicateFactory<T>를 상속하고RoutePredicateFactory접미사로 끝나는 이름을 가져야 합니다.package com.example.system.predicates; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.netty.util.internal.StringUtil; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; // 사용자 정의 라우트 단언 팩토리 // 1. 클래스 이름은 "{설정명}RoutePredicateFactory" 형식이어야 합니다. // 2. AbstractRoutePredicateFactory<설정 클래스>를 상속해야 합니다. @Component public class AgeRangeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRangeRoutePredicateFactory.Config> { public AgeRangeRoutePredicateFactory() { super(AgeRangeRoutePredicateFactory.Config.class); } // 설정 파일의 파라미터 순서를 정의합니다. (예: AgeRange=min,max) @Override public List<String> shortcutFieldOrder() { return Arrays.asList("minimumAge", "maximumAge"); } // 단언 로직을 구현합니다. @Override public Predicate<ServerWebExchange> apply(AgeRangeRoutePredicateFactory.Config config) { return exchange -> { // 람다 표현식을 사용하여 Predicate 구현 String ageParameter = exchange.getRequest().getQueryParams().getFirst("age"); if (!StringUtil.isNullOrEmpty(ageParameter)) { try { int currentAge = Integer.parseInt(ageParameter); return currentAge >= config.getMinimumAge() && currentAge <= config.getMaximumAge(); } catch (NumberFormatException e) { // 'age' 파라미터가 유효한 숫자가 아닐 경우 return false; } } // 'age' 파라미터가 없는 경우 기본적으로 허용 (또는 다른 정책 적용) return true; }; } // 설정 클래스: 설정 파일의 파라미터 값을 받을 때 사용됩니다. @Data @NoArgsConstructor public static class Config { private int minimumAge; // 최소 허용 나이 private int maximumAge; // 최대 허용 나이 } }3단계: 테스트
게이트웨이 애플리케이션을 실행하고 다음 URL로 테스트합니다:
http://localhost:8080/member/profile?age=35(허용)http://localhost:8080/member/profile?age=15(거부)
필터 (Filters)
필터는 요청의 전달 과정에서 요청과 응답에 다양한 수정 작업을 가할 수 있습니다.
필터의 주요 특성:
- 역할: 요청 데이터를 변경하거나, 응답 헤더를 추가하는 등의 작업을 수행합니다.
- 생명 주기:
- PRE 필터: 요청이 대상 서비스로 라우팅되기 전에 실행됩니다. 인증/인가, 로깅, 요청 파라미터 수정 등에 활용됩니다.
- POST 필터: 대상 서비스로부터 응답이 도착하고 클라이언트로 전달되기 전에 실행됩니다. 응답 헤더 추가, 응답 본문 변환, 통계 수집 등에 활용됩니다.
- 분류:
GatewayFilter(로컬 필터): 특정 라우트에만 적용되는 필터입니다.GlobalFilter(전역 필터): 모든 라우트에 적용되는 필터로, 별도 설정이 필요 없습니다.
로컬 필터 (GatewayFilter)
로컬 필터는 특정 라우트 또는 라우트 그룹에만 적용됩니다.
내장 로컬 필터 팩토리
Spring Cloud Gateway는 다양한 기능을 제공하는 여러 내장 로컬 필터 팩토리를 포함합니다.
AddRequestHeader: 요청 헤더에 지정된 필드를 추가합니다.filters: - AddRequestHeader=X-Correlation-ID, my-valueAddRequestParameter: 요청 파라미터에 지정된 필드를 추가합니다.filters: - AddRequestParameter=source, gatewayAddResponseHeader: 응답 헤더에 지정된 필드를 추가합니다.filters: - AddResponseHeader=X-Generated-By, SpringGatewayHystrix: Netflix Hystrix를 사용하여 서킷 브레이커 기능을 라우트에 적용합니다. 서비스 장애 시 대체(fallback) 경로를 지정할 수 있습니다.filters: - name: Hystrix args: name: myCircuitBreaker # Hystrix Command 이름 fallbackUri: forward:/fallback-endpoint # 장애 시 리다이렉트할 내부 경로Hystrix 전역 타임아웃 설정:
hystrix.command.myCircuitBreaker.execution.isolation.thread.timeoutInMilliseconds: 5000PrefixPath: 요청 경로에 지정된 접두사를 추가합니다.filters: - PrefixPath=/api/v1
내장 로컬 필터 사용 예시:
server: port: 8080 spring: application: name: api-gateway-app cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: discovery: locator: enabled: true routes: - id: item_service_route_filtered uri: lb://item-service-app predicates: - Path=/item-api/** filters: - StripPrefix=1 # 경로의 첫 번째 세그먼트 제거 - SetStatus=200 # 응답 HTTP 상태 코드를 200으로 강제 설정사용자 정의 로컬 필터
특정 라우트에 대해 고유한 로직을 적용해야 할 때 사용자 정의 로컬 필터를 구현합니다. 예를 들어, 요청이 들어올 때 콘솔과 내부 캐시에 로깅 메시지를 남기는 필터를 만들어 봅시다.
1단계: 설정 파일에
RequestTracing필터 추가server: port: 8080 spring: application: name: api-gateway-app cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: discovery: locator: enabled: true routes: - id: product_route_with_tracing uri: lb://product-api-service predicates: - Path=/product-api/** filters: - StripPrefix=1 - RequestTracing=true,false # 콘솔 로깅 활성화, 캐시 로깅 비활성화2단계: 사용자 정의 필터 팩토리 클래스 구현
package com.example.system.filters; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; // 사용자 정의 로컬 필터 팩토리 @Component public class RequestTracingGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTracingGatewayFilterFactory.Config> { public RequestTracingGatewayFilterFactory() { super(Config.class); } // 설정 파일의 파라미터 순서를 정의합니다. (예: RequestTracing=consoleEnabled,cacheEnabled) @Override public List<String> shortcutFieldOrder() { return Arrays.asList("consoleLoggingEnabled", "cacheLoggingEnabled"); } // 필터의 핵심 로직을 구현합니다. @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { if (config.isConsoleLoggingEnabled()) { System.out.println("콘솔 트레이싱이 활성화되었습니다. 요청 경로: " + exchange.getRequest().getURI().getPath()); } if (config.isCacheLoggingEnabled()) { // 실제 캐시 로깅 로직 구현 System.out.println("캐시 트레이싱이 활성화되었습니다."); } // 다음 필터 또는 라우팅 체인으로 요청을 전달합니다. return chain.filter(exchange); }; } // 설정 클래스: 설정 파라미터를 받기 위해 사용됩니다. @Data @NoArgsConstructor public static class Config { private boolean consoleLoggingEnabled; // 콘솔 로깅 활성화 여부 private boolean cacheLoggingEnabled; // 캐시 로깅 활성화 여부 } }3단계: 테스트
애플리케이션을 실행하고
http://localhost:8080/product-api/details/123으로 접근하면, 콘솔에 필터에서 정의한 로깅 메시지가 출력되는 것을 확인할 수 있습니다.전역 필터 (GlobalFilter)
전역 필터는 모든 라우트에 자동으로 적용되며, 별도의 설정이 필요 없습니다. 주로 모든 요청에 공통적으로 적용되어야 하는 권한 검증, 보안 검사, 감사 로깅 등에 활용됩니다.
내장 전역 필터
Spring Cloud Gateway는 내부적으로 다음과 같은 여러 전역 필터를 사용하여 요청 처리를 수행합니다.
NettyWriteResponseFilter: 게이트웨이를 통한 응답을 클라이언트에 다시 작성합니다.RouteToRequestUrlFilter: 수신된 요청의 URL을 게이트웨이가 전달할 실제 서비스 URL로 변환합니다.LoadBalancerClientFilter:lb://서비스_ID형태의 URI를 해석하여 로드 밸런싱 알고리즘을 통해 대상 서비스 인스턴스를 선택합니다.NettyRoutingFilter: NettyHttpClient를 사용하여 HTTP/HTTPS 요청을 대상 서비스로 전달합니다.ForwardRoutingFilter:forward://스킴을 사용하여 Gateway 인스턴스 내의 로컬 경로로 요청을 전달합니다.
사용자 정의 전역 필터
기업 환경에서는 모든 요청에 대해 통합된 인증 또는 권한 부여 로직이 필요할 때가 많습니다. 예를 들어, 모든 요청에
authToken쿼리 파라미터가 유효한지 확인하는 간단한 전역 인증 필터를 구현해봅시다.일반적인 인증 플로우:
- 클라이언트가 최초 서비스 요청 시, 서버는 사용자 정보를 인증합니다.
- 인증이 성공하면, 서버는 사용자 정보를 암호화한 토큰(예: JWT)을 생성하여 클라이언트에 반환합니다.
- 이후 모든 클라이언트 요청에는 이 인증 토큰이 포함됩니다.
- 서버는 토큰을 복호화하여 유효성을 검증합니다.
이러한 인증 로직의 유효성 검사 부분을 게이트웨이에서 통합적으로 처리할 수 있습니다. 다음은 요청 쿼리 파라미터에
auth_token이 존재하고 그 값이valid-secret일 때만 요청을 허용하고, 그렇지 않으면401 Unauthorized응답을 반환하는 전역 필터 예시입니다.package com.example.system.filters; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import reactor.netty.util.internal.StringUtil; // 사용자 정의 전역 필터는 GlobalFilter 및 Ordered 인터페이스를 구현해야 합니다. @Component public class AuthenticationGlobalFilter implements GlobalFilter, Ordered { // 필터의 핵심 로직을 구현합니다. @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String authenticationToken = exchange.getRequest().getQueryParams().getFirst("auth_token"); // "auth_token" 파라미터가 없거나 "valid-secret"과 일치하지 않으면 인증 실패 if (StringUtil.isNullOrEmpty(authenticationToken) || !authenticationToken.equals("valid-secret")) { System.out.println("인증 실패: 유효하지 않은 인증 토큰입니다. 요청 경로: " + exchange.getRequest().getURI().getPath()); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // HTTP 401 Unauthorized 응답 설정 return exchange.getResponse().setComplete(); // 응답을 완료하고 요청 처리를 중단합니다. } System.out.println("인증 성공: 요청을 다음 필터 체인으로 전달합니다."); // chain.filter()를 호출하여 다음 필터 또는 라우팅 체인으로 진행합니다. return chain.filter(exchange); } // 필터의 실행 순서를 정의합니다. 값이 작을수록 우선순위가 높습니다. @Override public int getOrder() { return -100; // 인증 필터는 일반적으로 다른 필터보다 높은 우선순위를 가집니다. } } - 예시: