분산 처리 환경

스프링 분산 처리 환경 6: Spring Cloud Gateway

ohji52 2025. 11. 15. 00:20

Spring Cloud Gateway

Spring Cloud Gateway는 MSA에서 클라이언트 요청의 단일 진입점 역할을 수행

클라이언트의 요청을 받아 라우팅 규칙에 따라 적절한 서비스로 요청을 전달하고 응답을 다시 클라이언트에게 전달하는 역할을 함

 

WebFlux

비동기 및 논블로킹 방식으로 동작하는 웹 프레임워크

소수의 스레드가 많은 요청을 효율적으로 처리. 요청 처리 중 작업에서 대기하는 동안 스레드를 해제하고 다른 요청 청리

 

Gradle

dependencies {
	
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
    
}
ext {
	set('springCloudVersion', "2023.0.0")
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

 

application.yml

server:
  port: 8000

spring:
  application:
    name: api-gateway-service
  cloud:
    gateway:
      routes:
        - id: auth-service-route
          uri: http://localhost:8080
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
            - AuthorizationHeaderFilter
jwt:
  secret:~

(라우팅 설정)

게이트웨이의 핵심 '이정표'

  • predicates: /auth/**로 들어오는 요청을 감지
  • filters: 요청이 전달되기 전 StripPrefix=1(경로 제거)과 커스텀 필터인 AuthorizationHeaderFilter를 순차적으로 실행

AuthorizationHeaderFilter.java

package com.example.distributed.filter;

import com.example.distributed.util.JwtTokenProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

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

    private final JwtTokenProvider jwtTokenProvider;
    
    public AuthorizationHeaderFilter(JwtTokenProvider jwtTokenProvider) {
        super(Config.class);
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            
            if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                return onError(exchange, "Authorization header missing", HttpStatus.UNAUTHORIZED);
            }
            
            List<String> authorizationHeaders = request.getHeaders().get(HttpHeaders.AUTHORIZATION);
            if (authorizationHeaders == null || authorizationHeaders.isEmpty()) {
                return onError(exchange, "Authorization header missing", HttpStatus.UNAUTHORIZED);
            }

            String authorizationHeader = authorizationHeaders.get(0);
            String jwt = authorizationHeader.replace("Bearer ", "");
            
            if (!jwtTokenProvider.validateToken(jwt)) {
                return onError(exchange, "JWT token is not valid or expired", HttpStatus.UNAUTHORIZED);
            }
            
            String userId = jwtTokenProvider.getUsername(jwt);
            
            ServerHttpRequest modifiedRequest = request.mutate()
                    .header("X-User-Id", userId) 
                    .build();

            return chain.filter(exchange.mutate().request(modifiedRequest).build());
        };
    }
    
    private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        System.err.println("API Gateway JWT Error: " + err);
        return response.setComplete();
    }

    public static class Config {
    }
}

 

(통합 인증 필터)

각 서비스마다 중복으로 존재하던 인증 로직을 게이트웨이로 모음

  • 토큰 검증: JwtTokenProvider를 사용해 모든 요청의 JWT 유효성을 입구에서 컷트
  • 헤더 변조 (Request Mutation): 토큰에서 꺼낸 userId를 X-User-Id라는 새로운 헤더에 담아 내부 서비스로 전달. 이를 통해 내부 서비스들은 복잡한 JWT 해석 없이 헤더값만 읽어서 사용자를 식별
  • 비동기 에러 처리: Mono<Void>를 반환하여 WebFlux 방식에 맞는 논블로킹 응답 처리를 수행

 

중앙 집중식 인증 및 보안 관리

  • 이점: 모든 마이크로서비스가 각자 인증 로직을 가질 필요가 없음
  • 실제 효과: 인증 로직에 수정이 필요할 때 게이트웨이의 필터 하나만 고치면 모든 서비스에 즉시 적용됩니다. 보안 취약점 노출 범위를 최소화
  1.  

내부 서비스의 단순화 (Service Abstraction)

  • 이점: 내부 서비스(Auth, User 등)는 더 이상 JWT나 보안 설정을 신경 쓰지 않아도 됨.
  • 실제 효과: 내부 서비스는 단순히 게이트웨이가 넘겨준 X-User-Id 헤더만 믿고 비즈니스 로직에만 집중하면 됩니다. 서비스 간 결합도가 획기적으로 낮아짐

고성능 비동기 처리 (WebFlux)

  • 이점: 동기(Blocking) 방식보다 훨씬 많은 연결을 동시에 수용 가능
  • 실제 효과: API Gateway는 모든 트래픽이 몰리는 병목 지점이 되기 쉬운데, WebFlux의 논블로킹 특성 덕분에 대규모 트래픽 상황에서도 시스템이 뻗지 않고 안정적으로 요청을 배분

클라이언트와의 통신 단일화

  • 이점: 클라이언트는 내부 서비스들의 주소(IP, 포트)를 알 필요가 없음
  • 실제 효과: 포트 8000번(Gateway) 하나만 알면 모든 기능을 이용할 수 있으며, 내부 서버의 위치가 바뀌거나 서비스가 증설되어도 클라이언트 코드를 수정할 필요가 없음
 

feat: Spring Cloud Gateway 도입 및 JWT 인증/인가 필터 구현 by 0hj1hyeon · Pull Request #5 · 0hj1hyeon/distributed

AuthorizationHeaderFilter를 구현

github.com