// FeignClient 를 Lazy Load 할 경우 HttpMessageConverters 문제로 아래 config 설정이 필요합니다.
// open issue https://github.com/spring-cloud/spring-cloud-openfeign/issues/235
@Configuration
public class FeignConfig {
@Bean
public Decoder feignDecoder() {
ObjectFactory<HttpMessageConverters> messageConverters = () -> {
HttpMessageConverters converters = new HttpMessageConverters();
return converters;
};
return new SpringDecoder(messageConverters);
}
}
FeignClient를 지연 로딩(Lazy Load)할 때 발생할 수 있는 HttpMessageConverters 관련 문제를 해결하기 위한 설정이다. FeignClient를 사용하여 다른 서비스와의 통신을 진행할때 HttpMessageConverters는 직렬화 역직렬화를 담당한다.
FeignClient를 지연로딩 한다면 실제로 그 메소드를 쓰기 전까지 빈에 등록되지 않는다.
-> 메모리 사용량을 아낀다, 애플리케이션 시작시간 단축
HttpMessageConverters : spring에서 http 요청/응답을 객체로 변환할 때 사용한다.
ObjectFactory messageConverters: HttpMessageConverters - FeignClient가 필요할 때 HttpMessageConverters를 적절하게 사용할 수 있게함
feignDecoder 메서드에서 이 디코더를 빈으로 등록함으로써, Feign이 이를 사용하여 HTTP 응답을 처리할 수 있게 된다.
server:
port: 19091
spring:
main:
web-application-type: reactive
application:
name: gateway-service
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/** # /order 로 시작하는 모든 요청은 eureka application name: order-service 로 호출되도록 설정합니다.
- id: product-service
uri: lb://product-service
predicates:
- Path=/products/** # /products 로 시작하는 모든 요청은 eureka application name: product-service 로 호출되도록 설정합니다.
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/auth/** # /auth 로 시작하는 모든 요청은 eureka application name: auth-service 로 호출되도록 설정합니다.
discovery:
locator:
enabled: true
service:
jwt:
secret-key: "401b09eab3c013"
eureka:
client:
service-url:
defaultZone: http://localhost:19090/eureka/
web-application-type: reactive : 리액티브 웹 애플리케이션으로 동작하도록 설정한다. Spring WebFlux를 기반, 비동기이다.
name: gateway-service : eureka에 등록할 때 사용함
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
- id: product-service
uri: lb://product-service
predicates:
- Path=/products/**
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/auth/**
discovery:
locator:
enabled: true
id: 각 라우트의 식별자
uri: 라우팅할 목적지 URI, lb://는 로드 밸런싱을 의미, Eureka에서 해당 서비스의 인스턴스를 동적으로 발견하고 로드 밸런싱하는 데 사용
predicates: 요청을 특정 조건에 따라 라우팅한다 여기서는 요청 경로(Path)가 특정 패턴(/order/, /products/, /auth/**)에 일치하는 경우 해당 마이크로서비스로 요청을 전달한다.
Discovery Locator: enabled: true로 설정하면 Gateway는 Eureka에 등록된 모든 서비스를 자동으로 탐지하고, 그에 따라 라우트를 동적으로 구성한다.
@Component
public class LocalJwtAuthenticationFilter implements GlobalFilter {
private final String secretKey;
private final AuthService authService;
// FeignClient 와 Global Filter 의 순환참조 문제가 발생하여 Bean 초기 로딩 시 순환을 막기 위해 @Lazy 어노테이션을 추가함.
public LocalJwtAuthenticationFilter(@Value("${service.jwt.secret-key}") String secretKey, @Lazy AuthService authService) {
this.secretKey = secretKey;
this.authService = authService;
}
// 필수 과제 - 외부 요청 보호 GlobalFilter
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
// 접근하는 URI 의 Path 값을 받아옵니다.
String path = exchange.getRequest().getURI().getPath();
// /auth 로 시작하는 요청들은 검증하지 않습니다.
if (path.startsWith("/auth")) {
return chain.filter(exchange);
}
String token = extractToken(exchange);
// 토큰이 존재하지 않거나, validateToken(token) 기준에 부합하지 않으면 401 에러를 응답합니다.
if (token == null || !validateToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
.... 이하부터는 토큰 관련 메소드
LocalJwtAuthenticationFilter : 모든 요청을 가로채서 JWT 토큰을 검증하고, 유효하지 않은 요청에 대해서는 401(Unauthorized) 상태 코드를 반환, GlobalFilter 인터페이스를 구현하여 Spring Cloud Gateway에서 모든 요청에 대해 전역적으로 작동하는 필터를 정의
@Lazy AuthService authService : 순환 참조 문제를 해결하기 위해서 사용됨, 빈의 초기 로딩 시점에 발생할 수 있는 순환 참조 문제를 방지
ServerWebExchange : 현재 HTTP 요청/응답과 관련된 정보에 접근할 수 있는 객체
GatewayFilterChain: 현재 필터 체인의 다음 필터를 호출하기 위한 객체
filter 메소드 동작 순서
public interface AuthService {
Boolean verifyUser(String userId);
}
@FeignClient(name = "auth-service")
public interface AuthClient extends AuthService {
@GetMapping("/auth/verify") // 유저 검증 API
Boolean verifyUser(@RequestParam(value = "user_id") String userId);
}