본문 바로가기
Programing/Java & Spring

스프링(Spring)에서 RestTemplate, Https 통신

by 슈퍼와이비 2023. 9. 8.
반응형

 

먼저 개발환경을 보겠습니다.

1. spring boot 3.1.1
2. httpClient 5

 

이 코드는 Spring Framework에서 RestTemplate을 사용하기 위한 구성 파일인 'RestTemplateConfig' 클래스를 정의하고 있습니다. RestTemplate은 HTTP 통신을 쉽게 수행할 수 있는 Spring의 클래스입니다. 아래는 코드의 각 부분을 설명합니다.

 

  1. '@Configuration': 이 어노테이션은 이 클래스가 Spring의 구성 파일임을 나타냅니다. 이 클래스에서 Bean을 정의하고 구성합니다.
  2. '@Bean' 어노테이션: 이 어노테이션은 해당 메서드가 Spring 컨테이너에 Bean을 생성하고 등록하는 메서드임을 나타냅니다.
  3. 'httpClient()' 메서드: 이 메서드는 HttpClient를 생성하고 구성합니다. HttpClient는 HTTP 요청을 수행하는 데 사용됩니다. 여기서 SSL 인증서 검증을 무시하도록 구성되며, 모든 인증서를 신뢰합니다. 이는 보안적으로 권장되지 않을 수 있으므로 주의해야 합니다. 또한 Https 인증 요청시 호스트네임 유효성 검사를 수행하지 않도록 설정합니다.
  4. 'factory(HttpClient httpClient)' 메서드: 이 메서드는 'HttpComponentsClientHttpRequestFactory' 를 생성하고 구성합니다. 이 Factory는 RestTemplate이 HTTP 클라이언트 요청을 보낼 때 사용하는 기본 설정을 구성합니다. 여기서는 연결 타임아웃을 3,000 밀리초로 설정하고, 위에서 생성한 'httpClient' 를 사용합니다.
  5. 'restTemplate(HttpComponentsClientHttpRequestFactory factory)' 메서드: 이 메서드는 'RestTemplate' 을 생성합니다. 'HttpComponentsClientHttpRequestFactory' 를 사용하여 RestTemplate이 HTTP 요청을 수행하도록 설정합니다. 이렇게 생성된 RestTemplate은 다양한 HTTP 요청을 수행하는 데 사용할 수 있습니다.
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

	@Bean
	HttpClient httpClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
		
		// 모든 인증서를 신뢰하도록 설정한다
		TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
		SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
		
		// Https 인증 요청시 호스트네임 유효성 검사를 진행하지 않게 한다.
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
		
		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
			.register("https", sslsf)
			.register("http", new PlainConnectionSocketFactory()).build();
		
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		httpClientBuilder.setConnectionManager(connectionManager);
		return httpClientBuilder.build();
		
	}
	
	@Bean
	HttpComponentsClientHttpRequestFactory factory(HttpClient httpClient) {
		HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
		factory.setConnectTimeout(3000);
		factory.setHttpClient(httpClient);
		
		return factory;
	}

	@Bean
	RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) {
		return new RestTemplate(factory);
	}

}

 

위에 설정한 RestTemplate Bean을 주입받는 Util Class를 생성합니다.

 

  1. @Autowired 어노테이션: RestTemplate 객체를 자동으로 주입 받습니다. 이 객체는 HTTP 요청을 수행하기 위해 사용됩니다.
  2. postForObject 메서드: 이 메서드는 POST 요청을 보내고 응답을 처리하는 주요 로직을 포함합니다. 메서드 시그니처는 다음과 같습니다.
    • url: 요청을 보낼 URL
    • queryParams: HTTP 요청의 쿼리 매개변수 (POST 요청 본문 데이터)
    • responseType: 요청을 통해 받을 응답 데이터의 타입을 나타내는 ParameterizedTypeReference
    • jwt: JWT (JSON Web Token) 인증 토큰
  3. UriComponentsBuilder: url을 기반으로 URI를 생성하기 위해 사용됩니다.
  4. HttpHeaders: HTTP 요청 헤더를 설정합니다. 이 코드에서는 Content-TypeAPPLICATION_FORM_URLENCODED로 설정하고, Bearer 토큰을 포함하는 인증 헤더를 설정합니다.
  5. restTemplate.exchange(): RestTemplate을 사용하여 HTTP 요청을 보내고 응답을 받습니다. uri.toUri()를 사용하여 URI로 변환하고, HttpMethod.POST를 사용하여 POST 요청을 보냅니다. 요청 헤더와 요청 본문 데이터를 포함하여 요청을 수행하고, responseType에 지정된 타입으로 응답 데이터를 받아옵니다.
  6. 예외 처리: 예외가 발생할 경우, 로그를 기록하고 에러 메시지를 응답으로 반환합니다. 주석 처리된 부분은 예외 발생 시 다른 처리 방법을 시도하려는 시도였을 것으로 보입니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class HttpUtil<T> {
	
	@Autowired
	private RestTemplate restTemplate;

	public T postForObject(String url, MultiValueMap<String, String> queryParams, ParameterizedTypeReference<T> responseType, String jwt) {

		T response = null;

		try {

			UriComponents uri = UriComponentsBuilder
					.fromUriString(url)
					.build();
			
			HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
			headers.setBearerAuth(jwt);
			
			ResponseEntity<T> responseEntity = restTemplate.exchange(
					uri.toUri(),
					HttpMethod.POST,
					new HttpEntity<>(queryParams, headers),
					responseType);

			response = responseEntity.getBody();

		} catch (Exception e) {
			log.error("err: {}", e.getMessage());
			String err = e.getMessage();
			response = (T) err;
		}

		return response;
	}
}

 

HttpUtil을 주입받아 JWT을 인증하는 코드 중 일부입니다.

 

이 코드는 JWT (JSON Web Token) 관리 및 로그인 프로세스를 처리하는 JwtTokenManager 클래스를 정의하고 있습니다. 이 클래스는 Spring의 @Component 어노테이션으로 Spring Bean으로 등록되어 관리되며, HttpUtil 클래스를 사용하여 HTTP 요청을 수행합니다. 아래는 코드의 주요 부분을 설명합니다

 

  1. apiUrl, queryParams, token 매개변수: API 엔드포인트 URL, 쿼리 매개변수, JWT 토큰을 입력으로 받습니다.
  2. ResultVo<ResultResponse, JwtTokenDto>: 이 부분은 요청 결과를 저장합니다.
  3. httpUtil.postForObject(url, queryParams, responseType, token): httpUtil 객체를 사용하여 HTTP POST 요청을 보냅니다. 이 요청은 url에 지정된 API 엔드포인트로 가고, queryParams는 POST 요청 본문 데이터로 전달됩니다. 요청을 보낼 때 token도 함께 전송됩니다. 이 때, 응답은 문자열로 받아옵니다.
  4. JSON 파싱: 받아온 문자열 응답을 JSON 형식으로 파싱합니다. ObjectMapper를 사용하여 JSON 문자열을 ResultVo<ResultResponse, JwtTokenDto> 객체로 변환합니다.
  5. resultVo 확인: 응답 데이터를 파싱한 resultVo 객체를 확인하여 로그인 성공 여부를 판단합니다. resultVogetResult().getCode()Const.LOGIN_SUCCESS_CODE와 일치하면 로그인이 성공한 것으로 간주합니다.
  6. 로그인 성공 시: 로그인이 성공하면 JWT 토큰에서 얻은 정보를 사용하여 Spring Security의 Authentication 객체를 생성하고, 이를 현재 실행 중인 스레드의 SecurityContextHolder에 설정합니다. 이렇게 하면 사용자가 인증되었음을 시스템에 알립니다.
  7. 로그인 실패 시: 로그인이 실패하면 BadCredentialsException을 던져서 로그인 실패를 처리합니다.
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtTokenManager {

    private final HttpUtil<String> httpUtil;
    
    @Value("${gateway.url}")
    private String gatewaySvr;

    public Authentication jwtLoginProcess(String apiUrl, MultiValueMap<String, String> queryParams, String token) {
        //토큰 만료시 
        ResultVo<ResultResponse, JwtTokenDto> resultVo = null;

        try {
            String url = "http://" + gatewaySvr + "/conn" + apiUrl;

            /*
            MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
            queryParams.add("id", 'test');
            queryParams.add("password", 'test@123');
            */
            
            ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<>() {};
            String res = httpUtil.postForObject(url, queryParams, responseType, token);

            ObjectMapper mapper = new ObjectMapper();
            resultVo = mapper.readValue(res, new TypeReference<ResultVo<ResultResponse, JwtTokenDto>>() {});

        } catch (Exception ex) {
            ex.printStackTrace();
            log.error("err : {}", ex.getMessage());
        }


        if ( resultVo != null 
                && Const.LOGIN_SUCCESS_CODE.equals(resultVo.getResult().getCode() ) ) {

            JwtTokenDto jwtTokenDto = resultVo.getData();
            Authentication newAuth = getAuthentication(jwtTokenDto);
            SecurityContextHolder.getContext().setAuthentication(newAuth);
            return newAuth;
        } else {
            // 로그인실패시 (아이디,비밀번호 오류 등)
            throw new BadCredentialsException(resultVo.getResult().getMessage());
        }
    }
}
반응형