Spring RestTemplate中禁用SSL证书验证

137

我有两个基于Spring的Web应用A和B,分别在两台不同的机器上。

我想从Web应用A向Web应用B发起HTTPS调用,但是,我在机器B上使用了自签名证书。因此我的HTTPS请求失败了。

在Spring中使用RestTemplate时,如何禁用HTTPS证书验证?我想要禁用验证,因为Web应用A和B都在内部网络中,但是数据传输必须通过HTTPS进行。

16个回答

110
@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;
 }

2
在您的项目中导入Apache HttpClient(>4.4):'org.apache.httpcomponents:httpclient:4.5.13' - bucky
我尝试了这个解决方案,但我仍然收到 Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到所请求目标的有效认证路径 的错误信息。 - Tobia

88

你需要做的基本上有两件事情,一是使用一个自定义的TrustStrategy来信任所有证书,二是使用NoopHostnameVerifier()来禁用主机名验证。这里是代码以及所有相关导入:

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;
}

6
TrustStrategy acceptingTrustStrategy = (X509Certificate[] x509Certificates, String s) ->true; 接受信任策略:acceptingTrustStrategy = (X509Certificate[] x509Certificates, String s) ->true; - Humoyun Ahmad
2
谢谢,这救了我的命。所有其他方法(使用keytool将导出的.crt添加到java cacerts、http://blog.codeleak.pl/2016/02/skip-ssl-certificate-verification-in.html以及其他5~6个stackoverflow帖子,包括此帖子的答案)都失败了,花费了我7个小时的时间。这个答案是我最后的希望,感谢。 - Gyuhyeon Lee
2
“NoopHostnameVerifier”非常重要——其他答案忽略了这一点。一个使用案例是通过环回到“localhost”来使用您的公共SSL证书。 - jocull
2
请提供支持此解决方案的Apache HTTP客户端库名称和版本,原问题询问的是Spring REST模板,据我所知它不会自动引入该库,谢谢。 - chrisinmtown
4
此可用于 4.5.13 版本 (https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient/4.5.13)。 - jaletechs
显示剩余2条评论

44

您需要添加的是自定义的HostnameVerifier类,绕过证书验证并返回true。

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

这需要适当地放置在你的代码中。


7
我正在使用Spring的RestTemplate类,你知道如何在RestTemplate中实现这个吗? - Prabhu R
8
实际上,这超出了RestTemplate的范畴。可以在http://forum.springsource.org/showthread.php?t=60854找到一些示例代码。还可以查看https://dev59.com/03I-5IYBdhLWcg3wsKl4。 - Raghuram
32
适当的地方在哪里,你能解释一下吗? - kamaci
10
明白了,这是翻译任务。以下是翻译后的内容:很有帮助,谢谢。顺便说一句,你可以简化成 lambda 表达式,像这样 HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); - saidfagan
1
就“适当的空间”而言,由于这是一个静态方法,您可以将其放入某些初始化代码中。例如,我将其放入了我的类中,该类扩展了WebSecurityConfigurerAdapter,并位于configure方法内部。 - Bostone
如果您正在使用PoolingHttpClientConnectionManager过程,上述方法无法正常工作。 这个方法对我有效: https://stackoverflow.com/a/19950935/1223253 - undefined

19

还有一种非常简单的方法可以做到这个技巧,而不需要导入任何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();

感谢您提供的替代方案,而不需要添加新的库。 - anjey

16

我找到了一种简单的方法

    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 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;

2
该导入哪个?太难理解了。 - sunsoft
1
我也会添加导入! - Yash Jagdale
1
已经过去两年了,你什么时候会添加导入? - Amit Kumar
错过了,现在添加了导入。 - Yash Jagdale

11

使用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;
           }
        }

NullHostnameVerifier verifier = new NullHostnameVerifier(); 你可以看到remember me cookie是一个字符串。 - Ran Adler
如果您正在使用PoolingHttpClientConnectionManager,上述过程无法正常工作。 对我来说,这个方法有效。 https://stackoverflow.com/a/19950935/1223253 - undefined

7
您可以使用HTTPClient API来实现此操作。
public RestTemplate getRestTemplateBypassingHostNameVerifcation() {
    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    return new RestTemplate(requestFactory);

}

5
这段代码出现了“sun.security.validator.ValidatorException: PKIX path building failed…”的错误,但同时要求跳过证书检查。 - socona
1
@socona 感谢您指出。正如方法名称中所提到的那样,这只是关闭主机名验证。 - Amit Parashar

5
完整的禁用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);
    }
});

2
不必依赖于(Apache)NoopHostnameVerifier。只需提供一个(简单的)HostnameVerifier的自定义实现,就像其他答案中所示的那样。这甚至可以是一个lambda,如:(hostname, session) -> true - Brent Bradburn
如何编写针对此代码的 JUnit 测试? - Mateen

3
这个问题涉及SSL连接。当您尝试连接到某些资源时,https协议要求创建安全连接。这意味着只有您的浏览器和网站服务器知道请求体中发送了什么数据。这种安全性是通过存储在网站上并由您的浏览器(或任何其他客户端,在我们的情况下是带有Apache Http Client的Spring RestTemplate)在与主机的第一次连接时下载的ssl证书实现的。有RSA256加密和许多其他很酷的东西。但在一天结束时:如果证书未注册或无效,则会看到证书错误(HTTPS连接不安全)。要修复证书错误,网站提供者需要为特定网站购买它或以某种方式进行修复,例如https://www.register.com/ssl-certificates 正确解决问题的方法
  • 注册SSL证书
不正确解决问题的方法
  • 从网站下载损坏的SSL证书

  • 将SSL证书导入到Java cacerts(证书存储)中

    keytool -importcert -trustcacerts -noprompt -storepass changeit -alias name -keystore "C:\Program Files\Java\jdk-11.0.2\lib\security\cacerts" -file file.cer

解决问题的“肮脏”(不安全)方式

  • make RestTemplate to ignore SSL verification

      @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();
          }
    

2
虽然这段代码可能提供了解决问题的方案,但强烈建议您提供有关为什么和/或如何回答问题的其他上下文信息。仅提供代码答案通常在长期内变得无用,因为未来遇到类似问题的观众无法理解解决方案背后的推理。 - palaѕн
感谢您抽出时间解释事情! - Abhi.P

3

如果要覆盖默认策略,您可以在连接了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()));
}

注意:一定要处理异常,因为此方法只会将异常抛出!

2
这对我来说在使用SB 2.2.4时与@Configuration中的RestTemplateBuilder连接起来很有效。另外:我使用了TrustAllStrategy而不是嵌套类。 - elonderin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接