1. API Gateway란?
Spring Cloud Gateway를 적용하기에 앞서 API Gateway가 무엇인지 알아보겠습니다.
기존 모놀리식 구조에서는 하나의 애플리케이션 안에서 요청을 처리하면 되었지만 MSA 구조에서는 각 서비스로 애플리케이션이 분리되기 때문에 클라이언트와 서비스 간 통신이 복잡해지는 문제가 발생하게 됩니다. 이를 해결하기 위해 사용하는 것이 API Gateway입니다.
API Gateway는 클라이언트와 마이크로서비스 간의 중간 관리자 역할을 수행하는 구성 요소입니다.
클라이언트의 요청은 API Gateway를 통과하며 Gateway는 요청을 적절한 서비스로 전달하고 응답을 클라이언트에 반환하는 역할을 합니다. API Gateway의 주요역할을 정리해보면 아래와 같습니다.
1. 중앙 집합점 역할
클라이언트는 마이크로서비스에 직접 접근하는 것이 아닌 API Gateway를 통해 접근합니다. 클라이언트가 마이크로서비스의 구조를 알 필요가 없어지고 변경에 대한 영향이 줄어듭니다.
2. 요청 라우팅
들어오는 요청을 기반으로, 어떤 서비스가 요청을 처리할지 판단하여 해당 서비스로 요청을 전달합니다.
3. 부하 분산
여러 인스턴스가 존재할 경우 요청을 분산시킵니다.
4. 인증, 인가, 로깅
모든 요청이 Gateway를 통하기 때문에 인증 / 인가와 같은 보안 정책이나, 로깅 등을 Gateway에서 통합 관리가 가능합니다.
위와 같은 장점이 존재하지만 단점 또한 존재합니다.
1. 단일 장애 지점
Gateway 서버에 장애가 생길 경우 전체 시스템이 영향을 받을 수 있다.
2. 추가 지연
요청이 Gateway를 통과해야 하므로 추가적인 트래픽이 발생한다.
지금까지 API Gateway의 장점과 단점에 대해 알아보았습니다.
다음으로 Spring에서 주로 사용하는 Spring Cloud Gateway에 대해 알아보겠습니다.
1-1 Spring Cloud Gateway
Spring Cloud Gateway는, API Gateway 역할을 수행하는 라이브러리로 Netty 기반의 비동기 방식으로 동작합니다.
Spring Cloud Gateway는 다음과 같은 방식으로 동작하게 됩니다.
1. 클라이언트는 Spring Cloud Gateway에 요청을 보냅니다.
2. Gateway Handler Mapping은 요청이 경로와 일치한다고 판단하면 Gateway Web Handler로 전달합니다.
3. Gateway WebHandler는 요청에 관련된 필터를 실행합니다.
4. 필터를 모두 실행한 후 원래 요청을 기반으로 프록시 요청을 생성하고 목적지 서비스로 라우팅합니다.
5. 응답이 Gateway로 들어오면 사후 필터 로직이 실행됩니다.
지금까지 API Gateway와, Spring Cloud Gateway에 대해 알아보았습니다.
지금부터는 본격적으로 Spring Cloud Gateway를 구성하는 방법에 대해 알아보겠습니다.
2. Spring Cloud Gateway 구현하기
2-1 새로운 모듈 생성 및 Gradle 의존성 추가하기
새로운 모듈을 생성해줍니다.
Spring Cloud Gateway와 Eureka Client 디펜던시를 추가해줍니다.
ext {
set('springCloudVersion', "2023.0.5")
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
2-2 application.yml 파일 작성하기
server:
port: 8000
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/users/**
- routes.id : 라우팅을 식별하기 위한 고유 id
- routes.uri: 요청 라우팅 대상 서비스의 URI
- routes.predicates : 들어오는 요청이 조건에 부합하는 경우 요청을 전달 (ex) /users로 요청이 들어올 경우 라우팅 진행)
2-3 Eureka Client 활성화하기
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
2-3 Postman을 통해 요청 확인하기
API Gateway의 8000번 포트로 요청을 보내면 정상적으로 요청이 처리되는 것을 볼 수 있습니다.
2-4 과정 요약
1. 클라이언트가 API Gateway에 요청을 보낸다.
2. Gateway는 설정을 기반으로 요청이 어떤 서비스로 라우팅되어야되는지 판단한다.
3. Eureka Server를 통해 해당서비스의 위치를 확인한다.
4. Gateway는 위치정보를 통해 요청을 전달하고 응답을 클라이언트에 전달한다.
3. JWT 필터 구현 및 적용하기
지금까지 Spring Cloud Gateway를 통해 요청에 대한 라우팅 처리를 진행했습니다.
이제 클라이언트 요청에 대한 인증을 처리하는 JWT 필터를 구현하고 적용해보도록 하겠습니다.
이 JWT 필터는 요청 헤더에 포함된 JWT를 검증해 유효한 요청인지 확인하고, 인증되지 않은 요청은 에러 응답을 반환하는 역할을 할 예정입니다!
진행할 과정은 다음과 같습니다.
1. JWT 필터 구현
- JWT를 추출하고 검증하는 AuthorizationHeaderFilter를 구현한다.
- 토큰의 유효성을 확인하고, 잘못된 요청에 대한 에러를 반환하는 메서드를 구성한다.
2. 필터를 Gateway에 적용
- AuthorizationHeaderFilter를 Spring Cloud Gateway의 라우팅 설정을 통해 인증 로직이 동작하도록 설정한다.
- 이 때 특정 라우트에만 필터를 적용하기 위해 Global 필터가 아닌 Custom 필터로 등록한다.
3. 테스트 및 검증
- 유효한 토큰과 유효하지 않은 토큰을 사용해 요청을 보내고, Gateway에서 올바른 응답이 반환되는지 확인한다.
다음으로는 본격적으로 필터를 구현하고 적용해보겠습니다.
3-1 인증 필터 구현하기
@Slf4j
@Component
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
@Value("${spring.token.jwtKey}")
private String jwtKey;
public AuthorizationHeaderFilter() {
super(Config.class);
}
public static class Config {
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
}
String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
String jwt = authorizationHeader.replace("Bearer ", "");
if (!isJwtValid(jwt)) {
return onError(exchange, "Token is not valid", HttpStatus.UNAUTHORIZED);
}
return chain.filter(exchange);
};
}
private Mono<Void> onError(ServerWebExchange exchange, String errMessage, HttpStatus httpStatus) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
log.error(errMessage);
return exchange.getResponse().setComplete();
}
private boolean isJwtValid(String jwt) {
byte[] secretKeyBytes = Base64.getDecoder().decode(jwtKey);
Key signingKey = Keys.hmacShaKeyFor(secretKeyBytes);
try {
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(signingKey).build().parseClaimsJws(jwt);
} catch (Exception e) {
return false;
}
return true;
}
}
필터의 동작 과정은 다음과 같습니다.
1. 요청 헤더에 Authorzation헤더가 존재하는지 확인한다.
2. 요청 헤더에 Authorzation가 존재하면 안에 담긴 값을 통해 Jwt 검증 메서드를 수행한다.
3. 토큰이 유효하면 요청을 수행하고, 유효하지 않으면 401에러를 반환한다.
3-2 application.yml에 필터 추가하기
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/users/**
- Method=GET,PATCH
filters:
- AuthorizationHeaderFilter
- id: user-service
uri: lb://user-service
predicates:
- Path=/users/**
인증이 필요한 곳에 filters을 통해 해당 필터를 사용하도록 추가해줍니다.
해당 설정을 통해 인증이 필요한 곳은 AuthorizationHeaderFilter가 수행되고, 요청을 진행하지 않을 서비스는 필터를 거치지 않도록 할 수 있습니다.
3-3 AuthorizationHeaderFilter 동작 테스트하기
1. 인증헤더를 넣지 않고 요청을 보낼 경우
2. 잘못된 토큰을 보내는 경우
3. 인증이 성공한 경우
인증이 성공한 경우 user-service에서 정상적으로 응답을 받아오는 것을 볼 수 있습니다.
지금까지 Spring Cloud Gateway를 구성하고 필터를 적용하는 방법에 대해 알아보았습니니다.
다음편으로는 설정파일을 관리하는 Spring Cloud Config에 알아보도록 하겠습니다.
참고
Spring Cloud로 개발하는 마이크로서비스 애플리케이션
Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA) 강의 | Dowon Lee - 인프런
Dowon Lee | Spring framework의 Spring Cloud 제품군을 이용하여 마이크로서비스 애플리케이션을 개발해 보는 과정입니다. Cloud Native Application으로써의 Spring Cloud를 어떻게 사용하는지, 구성을 어떻게 하는
www.inflearn.com
'Backend > MSA 전환' 카테고리의 다른 글
[MSA 전환하기 6편] OpenFeign을 활용한 서비스 간 통신하기 (0) | 2025.01.29 |
---|---|
[MSA 전환하기 5편] Spring Cloud Config 도입하기 (+ Spring Cloud Bus) (0) | 2025.01.20 |
[MSA 전환하기 3편] Service Discovery 적용하기 (0) | 2025.01.13 |
[MSA 전환하기 2편] 멀티 모듈 구성하기 (0) | 2025.01.11 |
[MSA 전환하기 1편] 모놀리식 아키텍처와 MSA란 무엇인가? (1) | 2024.11.26 |