sourcecode

Spring RestTemplate에서의 SSL 증명서 검증 디세블화

copyscript 2023. 3. 25. 11:44
반응형

Spring RestTemplate에서의 SSL 증명서 검증 디세블화

2대의 스프링 기반의 웹 앱 A와 B를 2대의 다른 머신에 탑재하고 있습니다.

웹 앱 A에서 웹 앱 B로 HTTPS 콜을 하고 싶은데, 머신 B에서 자기 서명 증명서를 사용하고 있습니다.따라서 HTTPS 요구는 실패합니다.

봄에 RestTemplate를 사용할 때 HTTPS 증명서 검증을 비활성화하려면 어떻게 해야 합니까?웹 앱 A와 B가 모두 내부 네트워크 내에 있지만 데이터 전송은 HTTPS를 통해 이루어져야 하므로 검증을 해제하고 싶습니다.

@Bean
public RestTemplate restTemplate() 
                throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

    SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
                    .loadTrustMaterial(null, acceptingTrustStrategy)
                    .build();

    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

    CloseableHttpClient httpClient = HttpClients.custom()
                    .setSSLSocketFactory(csf)
                    .build();

    HttpComponentsClientHttpRequestFactory requestFactory =
                    new HttpComponentsClientHttpRequestFactory();

    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    return restTemplate;
 }

기본적으로 필요한 것은 모든 증명서를 신뢰하는 커스텀 TrustStrategy 를 사용하는 것과 호스트명 검증을 디세블로 하기 위해서 NoopHostnameVerifier() 를 사용하는 것입니다.다음은 코드와 관련된 모든 Import입니다.

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
    SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    return restTemplate;
}

커스텀을 추가해야 합니다.HostnameVerifierclass는 증명서 검증을 바이패스하고 true를 반환합니다.

HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

이것은 당신의 코드에 적절히 입력되어야 합니다.

나는 간단한 방법을 찾았다.

    TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
    SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);

    RestTemplate restTemplate = new RestTemplate(requestFactory);

사용한 Import

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

cookie를 사용하여 내 응답 추가:

public static void main(String[] args) {
     MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
     params.add("username", testUser);
     params.add("password", testPass);
     NullHostnameVerifier verifier = new NullHostnameVerifier(); 
     MySimpleClientHttpRequestFactory requestFactory = new MySimpleClientHttpRequestFactory(verifier , rememberMeCookie);
     ResponseEntity<String> response = restTemplate.postForEntity(appUrl + "/login", params, String.class);

     HttpHeaders headers = response.getHeaders();
     String cookieResponse = headers.getFirst("Set-Cookie");
     String[] cookieParts = cookieResponse.split(";");
     rememberMeCookie = cookieParts[0];
     cookie.setCookie(rememberMeCookie);

     requestFactory = new  MySimpleClientHttpRequestFactory(verifier,cookie.getCookie());
          restTemplate.setRequestFactory(requestFactory);
}


public class MySimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory {

        private final HostnameVerifier verifier;
        private final String cookie;

        public MySimpleClientHttpRequestFactory(HostnameVerifier verifier ,String cookie) {
            this.verifier = verifier;
            this.cookie = cookie;
        }

        @Override
        protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
            if (connection instanceof HttpsURLConnection) {
                ((HttpsURLConnection) connection).setHostnameVerifier(verifier);
                ((HttpsURLConnection) connection).setSSLSocketFactory(trustSelfSignedSSL().getSocketFactory());
                ((HttpsURLConnection) connection).setAllowUserInteraction(true);
                String rememberMeCookie = cookie == null ? "" : cookie; 
                ((HttpsURLConnection) connection).setRequestProperty("Cookie", rememberMeCookie);
            }
            super.prepareConnection(connection, httpMethod);
        }

        public SSLContext trustSelfSignedSSL() {
            try {
                SSLContext ctx = SSLContext.getInstance("TLS");
                X509TrustManager tm = new X509TrustManager() {

                    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                    }

                    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                    }

                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                };
                ctx.init(null, new TrustManager[] { tm }, null);
                SSLContext.setDefault(ctx);
                return ctx;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }

    }


    public class NullHostnameVerifier implements HostnameVerifier {
           public boolean verify(String hostname, SSLSession session) {
              return true;
           }
        }

Apache 또는 알 수 없는 패키지를 가져오지 않고 매우 간단하게 이 트릭을 수행할 수 있는 또 다른 방법입니다.

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

    private void ignoreCertificates() {
    TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] certs, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType) {
        }
    } };

    try {
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    } catch (Exception e) {
     

    }

RestTemplate 앞에 ignoreCertificates()를 설정합니다.

   ignoreCertificates();
   RestTemplate restTemplate = new RestTemplate();

HTTP Client API와 함께 사용할 수 있습니다.

public RestTemplate getRestTemplateBypassingHostNameVerifcation() {
    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    return new RestTemplate(requestFactory);

}

SSL 호스트 이름 확인자를 사용하지 않도록 설정하는 전체 코드,

RestTemplate restTemplate = new RestTemplate();
//to disable ssl hostname verifier
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory() {
   @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
        if (connection instanceof HttpsURLConnection) {
            ((HttpsURLConnection) connection).setHostnameVerifier(new NoopHostnameVerifier());
        }
        super.prepareConnection(connection, httpMethod);
    }
});

기본 전략을 무시하려면 restTemplate를 배선하는 클래스에 간단한 메서드를 만듭니다.

 protected void acceptEveryCertificate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {

    TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            return true;
        }
    };

    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
            HttpClientBuilder
                    .create()
                    .setSSLContext(SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build())
                    .build()));
}

주의: 이 방법은 예외를 더 멀리 던질 뿐이므로 예외를 처리해야 합니다.

이 문제는 SSL 연결에 관한 것입니다.일부 리소스에 연결하려고 할 때 https 프로토콜은 보안 연결을 생성해야 합니다.즉, 브라우저와 웹 사이트 서버만이 요청 본문에 전송되는 데이터를 알 수 있습니다.이 보안은 웹 사이트에 저장되어 있으며 브라우저(또는 이 경우 Apache Http Client가 뒤에 있는 다른 클라이언트, Spring RestTemplate)에 의해 다운로드되어 호스트에 대한 첫 번째 접속으로 실현됩니다.RSA256 암호화 및 기타 유용한 기능이 많이 있습니다.하지만 결국:증명서가 등록되어 있지 않거나 무효인 경우는, 증명서 에러가 표시됩니다(HTTPS 접속은 시큐어하지 않습니다).증명서 오류를 수정하려면 웹사이트 프로바이더가 특정 웹사이트용으로 구입하거나 https://www.register.com/ssl-certificates와 같은 방법으로 수정해야 합니다.

문제를 해결하는 올바른 방법

  • SSL 증명서 등록

문제를 해결하는 올바른 방법이 아니다.

문제 해결 방법이 더럽다(안전하지 않다)

  • SSL 검증을 무시하도록 RestTemplate를 만듭니다.

      @Bean
      public RestTemplateBuilder restTemplateBuilder(@Autowired SSLContext sslContext) {
          return new RestTemplateBuilder() {
              @Override
              public ClientHttpRequestFactory buildRequestFactory() {
                  return new HttpComponentsClientHttpRequestFactory(
                          HttpClients.custom().setSSLSocketFactory(
                                  new SSLConnectionSocketFactory(sslContext
                                          , NoopHostnameVerifier.INSTANCE)).build());
              }
          };
      }
    
      @Bean
          public SSLContext insecureSslContext() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
             return SSLContexts.custom()
                      .loadTrustMaterial(null, (x509Certificates, s) -> true)
                      .build();
          }
    

보안: https/TLS 증명서 호스트명 체크를 무효로 합니다.다음 코드는 스프링 부트레스트 템플릿에서 동작합니다.

*HttpsURLConnection.setDefaultHostnameVerifier(
        //SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
        // * @deprecated (4.4) Use {@link org.apache.http.conn.ssl.NoopHostnameVerifier}
        new NoopHostnameVerifier()
);*

HttpClient > 4.3의 Java 코드 예시

package com.example.teocodownloader;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class Example {
    public static void main(String[] args) {
        CloseableHttpClient httpClient
                = HttpClients.custom()
                .setSSLHostnameVerifier(new NoopHostnameVerifier())
                .build();
        HttpComponentsClientHttpRequestFactory requestFactory
                = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
    }
}

덧붙여서, pom 파일에 다음의 의존 관계를 추가하는 것을 잊지 말아 주세요.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

HttpClient < 4.3 의 Java 코드의 예도 참조할 수 있습니다.

rest 템플릿을 사용하는 경우 이 코드를 사용할 수 있습니다.

    fun getClientHttpRequestFactory(): ClientHttpRequestFactory {
        val timeout = envTimeout.toInt()
        val config = RequestConfig.custom()
            .setConnectTimeout(timeout)
            .setConnectionRequestTimeout(timeout)
            .setSocketTimeout(timeout)
            .build()

        val acceptingTrustStrategy = TrustStrategy { chain: Array<X509Certificate?>?, authType: String? -> true }

        val sslContext: SSLContext = SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build()

        val csf = SSLConnectionSocketFactory(sslContext)

        val client = HttpClientBuilder
            .create()
            .setDefaultRequestConfig(config)
            .setSSLSocketFactory(csf)
            .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
            .build()
        return HttpComponentsClientHttpRequestFactory(client)
    }

    @Bean
    fun getRestTemplate(): RestTemplate {
        return RestTemplate(getClientHttpRequestFactory())
    }
@Bean
public RestTemplate restTemplate() throws Exception {
    SSLContext sslContext = new SSLContextBuilder()
            .loadKeyMaterial(keyStore, keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
            .loadTrustMaterial(trustStore, trustStorePassword.toCharArray(), (cert, authType) -> sslTrustStrategy)
            .build();
    HostnameVerifier hostnameVerifier = sslTrustStrategy ? new NoopHostnameVerifier() :
            SSLConnectionSocketFactory.getDefaultHostnameVerifier();
    SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
    HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
    HttpComponentsClientHttpRequestFactory httpComponentsHttpClientFactory =
            new HttpComponentsClientHttpRequestFactory(httpClient);
    RestTemplate restTemplate = new RestTemplate(httpComponentsHttpClientFactory);
    return restTemplate;
}

sslTrustStrategy = true이면

  1. (cert, authType) -> sslTrustStrategy에 의한 모든 증명서 신뢰
  2. 증명서 내의 호스트만 신뢰하는 것이 아니라 모두 신뢰합니다(NoopHostnameVerifier로 인해 신뢰하지 않으면 인증서 내의 호스트만 신뢰합니다).

언급URL : https://stackoverflow.com/questions/4072585/disabling-ssl-certificate-validation-in-spring-resttemplate

반응형