OAuth 로그인을 구현하기 위해 스프링에서 HTTP 통신을 위해 RedisTemplate을 사용했었는데 다른 방식도 존재함을 인지하게 되어, 블로그에 정리하려 해요. 이 글은 RestTemplate, WebClient 및 FeignClient를 다룰 거에요. 각 라이브러리의 사용법, 장점 그리고 어떤 상황에서 어떤 라이브러리를 선택해야 하는지에 대한 나만의 근거를 만드는 것이 목표에요.
스프링 공식문서는 다음과 같이 설명해요.
RestTemplate
is a synchronous client to perform HTTP requests. It is the original Spring REST client and exposes a simple, template-method API over underlying HTTP client libraries.
RestTemplate은 스프링 프레임워크의 일부로서, RESTful 웹 서비스와 통신하기 위한 편리한 클라이언트 라이브러리에요. RestTemplate을 사용하면 HTTP 요청을 쉽게 생성하고 서버로 보낼 수 있으며, HTTP 응답을 처리하는 기능으로 활용할 수 있어요.
RestTemplate은 요청과 응답을 Java 객체로 매핑하기 위한 다양한 컨버터를 제공하므로 JSON, XML 등 다양한 데이터 형식을 처리할 수도 있어요.
RestTemplate은 동기식으로 동작하기 때문에, 대량의 요청을 처리할 때는 성능 이슈가 발생할 수 있어요. 하지만 스프링 4부터 AsyncRestTemplate을 지원해주기 때문에 비동기로 처리할 수도 있게 되었어요.
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
restTemplate을 통해 요청을 보낸다는 것은 다음과 같은 내용을 포함하면 되요. (당연한 내용이지만)
public String requestAccessToken(OAuthLoginParams params) {
String url = authUrl + "/oauth2.0/token";
HttpEntity<MultiValueMap<String, String>> request = generateHttpRequest(params);
NaverToken naverToken = restTemplate.postForObject(url,
request,
NaverToken.class);
Objects.requireNonNull(naverToken);
return naverToken.accessToken();
}
private HttpEntity<MultiValueMap<String, String>> generateHttpRequest(OAuthLoginParams params) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = params.makeBody();
body.add("grant_type", OAuthConstant.GRANT_TYPE);
body.add("client_id", clientId);
body.add("client_secret", clientSecret);
return new HttpEntity<>(body, httpHeaders);
}
postForObject()
postForEntity()
ResponseEntity<NaverToken> response = restTemplate.postForEntity(url,
request,
NaverToken.class);
exchange()
exchange
메서드를 통해 HTTP 요청 메서드 (GET, POST, PUT, DELETE 등)와 다양한 HTTP 헤더를 설정할 수 있어요.exchange
메서드는 가장 유연한 방법으로 HTTP 요청을 수행할 수 있습니다. 요청 메서드, 헤더, 요청 본문, 그리고 기대하는 응답 형식을 모두 자유롭게 설정이 가능해요.HttpEntity<NaverToken> requestEntity = new HttpEntity<>(request, headers);
ResponseEntity<NaverToken> response = restTemplate.exchange(url,
request,
NaverToken.class);
요약하면, postForObject
는 응답을 객체로 변환하는 간단한 방법을 제공하고, postForEntity
는 HTTP 응답의 상세 정보를 처리하는 데 유용해요. exchange
는 가장 유연한 방법으로 HTTP 요청을 수행하며, 요청 및 응답 처리를 완전히 제어할 수 있게 되는 것이죠.
스프링 공식문서는 다음과 같이 설명해요.
WebClient
is a non-blocking, reactive client to perform HTTP requests. It was introduced in 5.0 and offers a modern alternative to theRestTemplate
, with efficient support for both synchronous and asynchronous, as well as streaming scenarios.In contrast to
RestTemplate
,WebClient
supports the following:
- Non-blocking I/O.
- Reactive Streams back pressure.
- High concurrency with fewer hardware resources.
- Functional-style, fluent API that takes advantage of Java 8 lambdas.
- Synchronous and asynchronous interactions.
- Streaming up to or streaming down from a server.
WebClient는 스프링 5부터 도입된 비동기 및 함수형 방식의 HTTP 클라이언트 라이브러리에요. WebClient는 네트워크 통신 작업을 비동기적으로 처리하고, 리액티브 프로그래밍 모델을 따르고 있어요. 이것은 스프링의 리액티브 프레임워크인 스프링 웹 플럭스와 함께 사용되어 비동기적이고 확장 가능한 웹 애플리케이션을 구축하는 데 적합해요.
implementation 'org.springframework.boot:spring-boot-starter-webflux'
public Mono<String> requestAccessToken(OAuthLoginParams params) {
String url = authUrl + "/oauth2.0/token";
return generateHttpRequest(params)
.flatMap(request -> webClient.post()
.uri(url)
.body(BodyInserters.fromValue(request))
.retrieve()
.bodyToMono(NaverToken.class)
.map(naverToken -> {
Objects.requireNonNull(naverToken);
return naverToken.accessToken();
}));
}
private Mono<MultiValueMap<String, String>> generateHttpRequest(OAuthLoginParams params) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = params.makeBody();
body.add("grant_type", OAuthConstant.GRANT_TYPE);
body.add("client_id", clientId);
body.add("client_secret", clientSecret);
return Mono.just(body);
}
requestAccessToken 메서드 과정을 공부해면,
generateHttpRequest
메서드에서는 OAuth 요청을 생성하는 MultiValueMap을 Mono로 반환하는 작업을 해요. (Mono는 비동기 작업을 나타내는 리액티브 형식의 객체에요.)flatMap
은 Mono의 값을 가져와서 다른 Mono나 Publisher로 변환해요. 여기서는 generateHttpRequest
가 반환한 Mono<MultiValueMap<String, String>>의 값을 가져오도록 해두었어요.webClient.post()
는 WebClient로 POST요청을 보낼 거에요..uri(url)
는 요청을 보낼 URI를 설정할 거에요..body(BodyInserters.fromValue(request))
는 요청 본문을 설정해요. request
는 generateHttpRequest
에서 반환한 MultiValueMap이며, BodyInserters.fromValue
를 사용하여 요청 본문으로 변환해요..retrieve()
는 서버로 요청을 보내고 응답을 가져오는 단계로 WebClient의 논블로킹 API 호출을 시작하게 되요..bodyToMono(NaverToken.class)
는 HTTP 응답 본문을 Mono로 변환하는 단계로 이때 NaverToken.class
를 지정하여 응답 본문을 NaverToken 클래스로 매핑하는 거에요..map(naverToken -> { ... })
는 Mono에 대한 매핑 단계로 여기서는 받아온 naverToken
객체가 null인지 확인하고, null이 아닌 경우에만 토큰을 추출하여 반환하기 위해 작성했어요.bodyToMono()
메서드는 HTTP 응답 본문을 Mono로 변환해요. 이 Mono는 본문 데이터만을 포함하며, HTTP 응답의 상태 코드, 헤더 등에는 관심이 없어요.webClient.get().uri(url).retrieve().bodyToMono(String.class)
toEntity()
메서드는 HTTP 응답을 ResponseEntity로 변환해요. 이 ResponseEntity는 HTTP 응답의 모든 정보를 포함하며, 상태 코드, 헤더, 본문 데이터를 포함한 것이에요.webClient.get().uri(url).retrieve().toEntity(NaverToken.class)
스프링 공식문서는 다음과 같이 설명해요.
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same
HttpMessageConverters
used by default in Spring Web. Spring Cloud integrates Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
FeignClient는 스프링 클라우드 프레임워크의 일부로, 마이크로서비스 간 통신을 쉽게 구현할 수 있는 라이브러리에요. 주로 마이크로서비스 아키텍처에서 다른 마이크로서비스와의 통신을 위해 사용되며, 스프링 애플리케이션과 통합하여 사용하기 용이해요.
FeignClient는 Netflix에서 개발한 방식인데, 현재는 오픈 소스로 전환되었으며 SpringCloud 프레임워크 프로젝트 중 하나로 들어갔다고 해요.
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign'
feign:
client:
access-token:
url: // ...
user-profile:
url: // ...
@SpringBootApplication
@EnableFeignClients
public class GivemeticonApplication {
public static void main(String[] args) {
SpringApplication.run(GivemeticonApplication.class, args);
}
}
@FeignClient(name = "ouath-access-token", url = "${feign.client.access-token.url}")
public interface AccessTokenFeignClient {
@PostMapping("/login/oauth/access_token")
String getAccessToken(
@RequestParam("client_id") String clientId,
@RequestParam("client_secret") String clientSecret,
@RequestParam("code") String code
);
}
FeignClient 는 interface 형식으로 정의하여 REST API 요청을 하는 방식이에요.
이 방식의 과정이 Spring Data JPA 의 JpaRepository를 상속받아 사용하는 interface와 유사한 방식이라고 생각했어요.
정말 간단하게, 파라미터의 @RequestParam
을 통해 RequestBody의 body 를 설정할 수 있어요.
추천 방법은 사용하는 환경, 프로젝트의 규모 및 복잡성, 개발자의 경험과 선호도 등에 따라 다를 수 있다고 생각해요.
RestTemplate는 간단하고 편리한 접근성을 가지고 있으며,
WebClient는 비동기 및 리액티브 처리를 필요로 하는 경우 유용하며,
FeignClient는 선언적인 방식으로 REST 클라이언트를 작성하고 통신을 자동화하는 데 유리해요.
최신 Spring Framework 버전에서는 WebClient와 OpenFeign을 권장하고 있어요. 또한, 비동기 처리와 리액티브 프로그래밍에 익숙해져 있다면 WebClient를 사용하는 것이 더욱 효과적라고 생각해요. 대량의 요청을 처리하려다 보니 스프링에서 비동기식 방식을 권장하는 것 같아요.
무엇보다도 프로젝트의 특성과 개발자의 편의성을 고려하여 적합한 방법을 선택하는 것이 현명한 선택이라고 될 것이라고 생각해요 !!
REST Clients
Spring Cloud OpenFeign
Spring Boot Github 소셜 로그인 구현하기 [ RestTemplate · WebClient · FeignClient 를 비교해보자 ]