본문 바로가기
Java & Spring

리소스 서버 분리 & API Gateway 연동(Spring Cloud Gateway)

by yamoojin83 2025. 10. 15.

리소스 서버 분리 & API Gateway 연동(Spring Cloud Gateway)

단일 애플리케이션에서 모놀리식으로 인증/인가를 처리하다가 게이트웨이 + 마이크로서비스 구조로 바꾸면, 어디에서 인증을 검증하고 권한을 어떻게 전파할지가 핵심 이슈가 됩니다. 이 글은 Spring Cloud Gateway를 프런트 도어로 두고, 백엔드는 Resource Server(JWT)로 나누는 실전 패턴을 제공합니다.

1) 기본 전략: “게이트웨이도 검증, 백엔드도 검증”

  • 게이트웨이: Authorization: Bearer ...를 받아 JWT 유효성을 1차 검증(만료/서명/클레임).
  • 백엔드(리소스 서버): 게이트웨이를 통과한 요청이라도 다시 검증합니다. 서비스 간 직접 호출, 내부 노출 등 우발적 바이패스를 막습니다.
  • 권한 전파: Authorization 헤더 그대로 전달(권장). 혹은 X-Principal, X-Roles 등 헤더로 파생 전파(민감, 신중).

2) 게이트웨이 설정: JWT 리소스 서버 + 라우팅


# application.yml (Gateway)
server:
  port: 8080

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://auth.example.com/.well-known/jwks.json  # 5편 구성 재사용

  cloud:
    gateway:
      default-filters:
        - RemoveRequestHeader=Cookie
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials
      routes:
        - id: user-service
          uri: http://user:8081
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1   # /api 제거 → /users/** 로 전달
        - id: order-service
          uri: http://order:8082
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

게이트웨이는 자체적으로 JWT를 검증만 하고, 권한 판단은 백엔드가 상세히 수행합니다. 공통 CORS/보안 헤더는 게이트웨이에서 통일하면 편합니다.

3) 백엔드 서비스: Spring Security Resource Server


# application.yml (User Service)
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://auth.example.com/.well-known/jwks.json

@Configuration
@EnableMethodSecurity
public class SecurityCfg {

  @Bean
  SecurityFilterChain http(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(a -> a
          .requestMatchers(HttpMethod.GET, "/users/me").hasAnyRole("USER","ADMIN")
          .requestMatchers("/admin/**").hasRole("ADMIN")
          .anyRequest().authenticated())
        .oauth2ResourceServer(o -> o.jwt());
    return http.build();
  }
}

@PreAuthorize로 도메인 권한까지 세밀 제어합니다. (7편의 권한 모델링 글 참고)

4) Token Relay(선택): 게이트웨이가 토큰을 갱신/주입

OAuth2 Client로 게이트웨이가 동작하며, 백엔드로 액세스 토큰을 릴레이하는 패턴도 있습니다. 그러나 대부분의 BFF/API 시나리오는 클라이언트 → 게이트웨이 → 백엔드로 동일 토큰을 그대로 전달하는 게 단순하고 안전합니다.

5) 401/403 처리 표준화


@Configuration
public class ExceptionHandlers {

  @Bean
  SecurityWebFilterChain gateway(HttpSecurity http) throws Exception {
    http.exceptionHandling(ex -> ex
        .authenticationEntryPoint((req, res, e) -> {
          res.setStatusCode(HttpStatus.UNAUTHORIZED);
        })
        .accessDeniedHandler((req, res, e) -> {
          res.setStatusCode(HttpStatus.FORBIDDEN);
        }));
    return http.build();
  }
}

클라이언트는 401 = 로그인 필요, 403 = 권한 없음으로 분기합니다. 메시지·코드 규격을 시리즈 전반에서 통일하세요.

6) CORS/보안 헤더는 게이트웨이에서 1곳 관리


spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          "[/**]":
            allowedOrigins: "https://app.example.com"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

백엔드들마다 따로 CORS를 두지 말고, 게이트웨이에서 단일 소스로 관리하는 것이 운영 안정적입니다.

7) 권장 토폴로지와 트러블슈팅

  • HTTPS 종단: 외부는 게이트웨이에서 종단, 내부도 TLS 원칙 적용을 권장.
  • 헤더 보존: Authorization, Trace-Id, X-Request-Id 보존.
  • 시간 동기화: JWT 만료/발급 스큐를 줄이려 모든 노드 NTP 동기화.
  • 키 회전: 5편의 JWK 키 회전 정책을 게이트웨이/백엔드 모두 적용.

 

 

👉 1편: SecurityFilterChain 완전정복: 요청 매칭/인가 룰 제대로 이해하기

👉 2편: PasswordEncoder와 회원가입: BCrypt·Pepper·비밀번호 정책

👉 3편: AuthenticationProvider & UserDetailsService 커스터마이징

👉 4편: 세션 기반 로그인 vs Stateless: CSRF/Remember-me/세션관리

👉 5편: JWT 심화: 만료/클레임/키 회전 + 4편과의 연결 전략

👉 6편: OAuth2 로그인(Google/GitHub) + JWT 브릿지

👉 7편: 권한 모델링: ROLE vs 권한 문자열, @PreAuthorize, 도메인 권한

👉 8편: CORS·XSS·헤더 보안: SPA/REST 현실 설정

👉 9편: 리소스 서버 분리 & API Gateway 연동(Spring Cloud Gateway)

👉 10편: 감사 로깅/Audit: 로그인 실패·권한거부 탐지 규격