
MSA에서는 서비스가 독립적이기 때문에
“어디서 인증을 하고, 각 서비스는 무엇을 믿을 것인가”를 먼저 정하는 게 중요하다.
각 모듈(서비스)이 클라이언트가 보낸 JWT 토큰을 직접 검증하고, 그 결과로 SecurityContext를 구성하는 방식이다.
장점
방어 심층(Defense in Depth)
보안 완전성
단점
코드 중복
성능 부하
요약
→ 보안을 최우선으로 볼 때 적합하지만, 코드·성능 비용을 감수해야 하는 전략
API Gateway에서 JWT 유효성 검증을 먼저 수행하고,
사용자 핵심 정보만 헤더로 실어 내부 서비스로 넘기는 방식이다.
예:
X-User-Id, X-User-Role 등을 헤더에 추가해서 전달
장점
코드 중복 감소
일관된 보안 모델
성능 이점
단점 / 주의점
Gateway가 단일 신뢰 지점이 됨
내부 서비스는 “헤더를 기반으로 한 Pre-authentication” 필터 정도는 유지하는게 좋음
요약
→ 실무에서 가장 많이 쓰이는 패턴
→ 인증은 Gateway 집중, 서비스는 전달된 정보로 인가에 집중
역할
구현 포인트
X-User-* 형태로 정보 추가역할
Gateway가 전달한 헤더 기반으로 SecurityContext 구성
HeaderAuthenticationFilter 같은 커스텀 필터에서
PreAuthenticatedAuthenticationToken 생성 → SecurityContext에 저장서비스별 인가 로직 적용 (메소드/엔드포인트 단위)
구현 포인트
@PreAuthorize("hasRole('ADMIN')") 같은 메소드 보안 활용MSA에서는 서비스끼리도 서로 호출한다. Spring Cloud에서는 대표적으로 세 가지 방법을 쓴다.
discoveryClient.getInstances("order-service")@Component
public class OrderDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public Order getOrder(String orderId) {
RestTemplate restTemplate = new RestTemplate();
List<ServiceInstance> instances =
discoveryClient.getInstances("order-service");
if (instances.isEmpty()) return null;
String serviceUri = String.format(
"%s/v1/order/%s",
instances.get(0).getUri().toString(),
orderId
);
ResponseEntity<Order> restExchange =
restTemplate.exchange(
serviceUri,
HttpMethod.GET,
null,
Order.class,
orderId
);
return restExchange.getBody();
}
}
ServiceInstance에 호스트, 포트, URI 정보가 들어있다.RestTemplate으로 직접 호출→ 교육용/실험용에는 괜찮지만, 실무에선 잘 안 쓰는 패턴
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@Component
public class OrderRestTemplateClient {
@Autowired
private RestTemplate restTemplate;
public Order getOrder(String orderId) {
ResponseEntity<Order> restExchange =
restTemplate.exchange(
"http://order-service/v1/order/{orderId}",
HttpMethod.GET,
null,
Order.class,
orderId
);
return restExchange.getBody();
}
}
URL에서 http://order-service 는 실제 호스트/포트가 아니라 서비스 이름
@LoadBalanced가 붙은 RestTemplate이
@FeignClient(name = "order-service") // Eureka 서비스 이름
public interface OrderFeignClient {
@RequestMapping(
method = RequestMethod.GET,
value = "/v1/orders/{orderId}"
)
Order getOrder(@PathVariable("orderId") String orderId);
}
@FeignClient(name = "order-service")
order-service로 호출@RequestMapping 방식은 Spring MVC와 동일
사용 예:
@Service
public class OrderService {
private final OrderFeignClient orderFeignClient;
public OrderService(OrderFeignClient orderFeignClient) {
this.orderFeignClient = orderFeignClient;
}
public Order getOrder(String orderId) {
return orderFeignClient.getOrder(orderId);
}
}
Feign 클라이언트 사용 시
FeignException으로 던져짐필요하면 커스텀 ErrorDecoder를 만들어
HTTP 상태 코드 + 응답 바디를 파싱해서
도메인에 맞는 예외로 매핑 가능
인증 처리
서비스 간 통신
DiscoveryClient + RestTemplate (실무 비추천)@LoadBalanced RestTemplateMSA에서는 “어디서 인증을 하고, 서비스끼리는 어떻게 신뢰할 것인가”와
“서비스 간 통신을 어떤 레벨의 추상화로 가져갈 것인가”를 먼저 정해두면
전체 구조가 훨씬 깔끔하게 정리된다.