source

ssl을 사용하는 Spring 5 WebClient

manysource 2023. 8. 13. 09:48

ssl을 사용하는 Spring 5 WebClient

Web Client 사용 사례를 찾고 있습니다.

Spring 5 WebClient를 사용하여 https 및 자체 서명 인증서를 사용하여 REST 서비스를 쿼리하는 것이 목표입니다.

예를 들어볼까요?

Spring 5.1.1(Spring boot 2.1.0)이 제거된 것 같습니다.HttpClientOptions부터ReactorClientHttpConnector의 인스턴스를 생성하는 동안 옵션을 구성할 수 없습니다.ReactorClientHttpConnector

이제 작동하는 한 가지 옵션은 다음과 같습니다.

val sslContext = SslContextBuilder
            .forClient()
            .trustManager(InsecureTrustManagerFactory.INSTANCE)
            .build()
val httpClient = HttpClient.create().secure { t -> t.sslContext(sslContext) }
val webClient = WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()

기본적으로 HttpClient를 생성하는 동안 insecureslContext를 구성한 다음 사용할 httpClient를 전달합니다.ReactorClientHttpConnector세계적으로

다른 옵션은 구성하는 것입니다.TcpClient안전하지 않은 sslContext를 사용하여 생성합니다.HttpClient아래 그림과 같이 인스턴스:

val sslContext = SslContextBuilder
            .forClient()
            .trustManager(InsecureTrustManagerFactory.INSTANCE)
            .build()
val tcpClient = TcpClient.create().secure { sslProviderBuilder -> sslProviderBuilder.sslContext(sslContext) }
val httpClient = HttpClient.from(tcpClient)
val webClient =  WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()

자세한 정보:

업데이트: 동일한 코드의 Java 버전

SslContext context = SslContextBuilder.forClient()
    .trustManager(InsecureTrustManagerFactory.INSTANCE)
    .build();
                
HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(context));

WebClient wc = WebClient
                    .builder()
                    .clientConnector(new ReactorClientHttpConnector(httpClient)).build();

확인 없이 모든 X.509 인증서(자체 서명 포함)를 신뢰하는 안전하지 않은 TrustManagerFactory 사용 예를 참조하십시오.설명서의 중요한 참고 사항:

프로덕션에서 이 TrustManagerFactory를 사용하지 마십시오.그것은 순전히 테스트 목적이기 때문에 매우 불안정합니다.

@Bean
public WebClient createWebClient() throws SSLException {
    SslContext sslContext = SslContextBuilder
            .forClient()
            .trustManager(InsecureTrustManagerFactory.INSTANCE)
            .build();
    ClientHttpConnector httpConnector = HttpClient.create().secure(t -> t.sslContext(sslContext) )
    return WebClient.builder().clientConnector(httpConnector).build();
}

스프링 부트 2.0->2.1 변경 사항을 수용하기 위해 이를 편집해야 했습니다.

프로덕션 코드를 프로그래밍하려는 또 다른 방법은 신뢰 저장소 및 키 저장소가 있는 위치에 대한 스프링 부트 서버의 설정을 사용하여 주입된 WebClient를 수정하는 것입니다.클라이언트에서 2-way-ssl을 사용하는 경우 Keystore만 제공하면 됩니다.ssl-stuff가 정말 멋진 스프링 부트 서버 설정과 유사하게 사전 구성되지 않고 쉽게 주입할 수 없는 이유는 확실하지 않습니다.

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
.
.
.

  @Bean
  WebClientCustomizer configureWebclient(@Value("${server.ssl.trust-store}") String trustStorePath, @Value("${server.ssl.trust-store-password}") String trustStorePass,
      @Value("${server.ssl.key-store}") String keyStorePath, @Value("${server.ssl.key-store-password}") String keyStorePass, @Value("${server.ssl.key-alias}") String keyAlias) {

      return (WebClient.Builder webClientBuilder) -> {
          SslContext sslContext;
          final PrivateKey privateKey;
          final X509Certificate[] certificates;
          try {
            final KeyStore trustStore;
            final KeyStore keyStore;
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
            keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());
            List<Certificate> certificateList = Collections.list(trustStore.aliases())
                .stream()
                .filter(t -> {
                  try {
                    return trustStore.isCertificateEntry(t);
                  } catch (KeyStoreException e1) {
                    throw new RuntimeException("Error reading truststore", e1);
                  }
                })
                .map(t -> {
                  try {
                    return trustStore.getCertificate(t);
                  } catch (KeyStoreException e2) {
                    throw new RuntimeException("Error reading truststore", e2);
                  }
                })
                .collect(Collectors.toList());
            certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
            privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray());
            Certificate[] certChain = keyStore.getCertificateChain(keyAlias);
            X509Certificate[] x509CertificateChain = Arrays.stream(certChain)
                .map(certificate -> (X509Certificate) certificate)
                .collect(Collectors.toList())
                .toArray(new X509Certificate[certChain.length]);
            sslContext = SslContextBuilder.forClient()
                .keyManager(privateKey, keyStorePass, x509CertificateChain)
                .trustManager(certificates)
                .build();
  
            HttpClient httpClient = HttpClient.create()
                .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
            ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
            webClientBuilder.clientConnector(connector);
          } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e) {
            throw new RuntimeException(e);
          }
        };
  }

웹 클라이언트를 사용하는 부분은 다음과 같습니다.

import org.springframework.web.reactive.function.client.WebClient;

@Component
public class ClientComponent {

  public ClientComponent(WebClient.Builder webClientBuilder, @Value("${url}") String url) {
    this.client = webClientBuilder.baseUrl(solrUrl).build();
  }
}

이것은 나에게 효과가 있습니다.Spring 프레임워크 버전 5.3.23(Spring boot 버전 2.7.4) ReactorClientHttpConnector를 사용하여 다음과 같은 방법을 시도할 수 있습니다.

SslContext context = SslContextBuilder.forClient()
    .trustManager(InsecureTrustManagerFactory.INSTANCE)
    .build();
                
HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(context));

WebClient wc = WebClient
                    .builder()
                    .clientConnector(new ReactorClientHttpConnector(httpClient)).build();

이 대답이 당신에게 도움이 되기를 바랍니다.

반응형 WebFlux webClient를 사용하여 https로 보호되는 REST API를 사용하는 방법을 고수했을 수 있는 사람을 위한 것입니다.

당신은 두 가지를 창조하기를 원합니다.

  1. https 사용 REST API - https://github.com/mghutke/HttpsEnabled
  2. 위의 REST API를 사용할 WebClient가 있는 클라이언트로서의 또 다른 REST API - https://github.com/mghutke/HttpsClient

참고: 위의 프로젝트를 통해 위의 스프링 부트 앱과 공유된 키 저장소를 확인하십시오.그리고 프로그래밍 방식으로 키 매니저 팩토리와 트러스트 매니저 팩토리를 추가했습니다.

또한 예를 들어 여러 SSL 컨텍스트를 구성해야 하는 경우 REST API 1과 REST API 2가 있으며 이를 위해 구성했습니다.SSLContext sslContext1그리고.SSLContext sslContext2

중요한 것은HttpClient.create().secure(...)하나만 적용할 수 있습니다.SSLContext하지만 우리의 경우에는 여러 개를 원합니다.

그래서, 우리의 경우의 해결책은 두 개의 다른 것을 만드는 것입니다.WebClient 른다로ReactorClientHttpConnector

// for REST API 1 with sslContext1
WebClient webClient1 = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(
                HttpClient.create()
                        .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext1))
        ))
        .build();

// for REST API 1 with sslContext2
WebClient webClient2 = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(
                HttpClient.create()
                        .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext2))
        ))
        .build();

그리고 우리는 끝!

또한 이러한 클라이언트는 기본적으로 이벤트 루프 그룹을 공유하는 것이 좋습니다.그러나 다음을 사용하여 구성하면 그렇지 않습니다.runOn 는또를사여하를 하여 사용합니다.ReactorResourceFactory리소스에 대한 자세한 내용은 https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client-builder-reactor-resources 에서 확인할 수 있습니다.

언급URL : https://stackoverflow.com/questions/45418523/spring-5-webclient-using-ssl