评论

收藏

[Java] 使用RestTemplate调用https接口跳过证书验证

编程语言 编程语言 发布于:2022-03-07 16:36 | 阅读数:608 | 评论:0

这篇文章主要介绍了使用RestTemplate调用https接口跳过证书验证,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
RestTemplate调用https接口跳过证书验证
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; 
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException; 
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate; 
public class NoHttpsClientUtils {
  /**
   * 跳过证书效验的sslcontext
   * 
   * @return
   * @throws Exception
   */
  private static SSLContext createIgnoreVerifySSL() throws Exception {
    SSLContext sc = SSLContext.getInstance("TLS");
    // 实现一个X509TrustManager接口,用于绕过验证,不用修改里面的方法
    X509TrustManager trustManager = new X509TrustManager() {
      @Override
      public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
          String paramString) throws CertificateException {
      }
      @Override
      public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
          String paramString) throws CertificateException {
      }
      @Override
      public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
      }
    };
    sc.init(null, new TrustManager[] { trustManager }, null);
    return sc;
  }
  /**
   * 构造RestTemplate
   * 
   * @return
   * @throws Exception
   */
  public static RestTemplate getRestTemplate() throws Exception {
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    // 超时
    factory.setConnectionRequestTimeout(5000);
    factory.setConnectTimeout(5000);
    factory.setReadTimeout(5000);
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(createIgnoreVerifySSL(),
        // 指定TLS版本
        null,
        // 指定算法
        null,
        // 取消域名验证
        new HostnameVerifier() {
          @Override
          public boolean verify(String string, SSLSession ssls) {
            return true;
          }
        });
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    factory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(factory);
    // 解决中文乱码问题
    restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
    return restTemplate;
  }
}
RestTemplate访问https遇到SSL证书验证错误
今天,遇到了一个SSL证书认证的问题,纠结了好久才解决,学到了很多东西,记录下来,分享给大家。
首先说一下大概的背景,我们的项目对接了第三方接口,需要向对方发送https请求。那么问题来了,https请求时需要通过SSL证书认证的,今天流程突然走不下去了,看日志发现是SSL认证的问题:
DSC0000.png

在网上找资料,解决问题有两个思路:
1.通过相关配置,跳过SSL证书验证,完成请求。
2.下载证书,将证书导入到JDK的证书管理里面。
第一种方法相当于是取巧,逃避问题,我们当然不能那么做了,我们要迎接挑战,所以小编直接pass第一条,选择第二条。
添加Https证书
下载证书
先通过浏览器将未签名验证的证书保存到本地, 具体步骤:
DSC0001.jpg

点击 不安全C> 证书C> 详细信息 --> 复制到文件 然后默认选择 起一个文件名 , 保存即可。
(Mac下载:Mac找不到复制的按钮。。。只能另辟蹊径了)点地址栏上的小锁,然后点证书,然后将蓝色的文件拖到要下载的文件夹即可。最好给它重命名方便使用。
导入证书
我们需要将证书导入到JDK的证书管理里面才能咋项目中使用我们刚刚下载的证书。导入命令如下:
keytool -import -noprompt -trustcacerts -alias  -keystore
/Users/ZHONGHUI/develop/jdk-12.0.2.jdk/Contents/Home/lib/security/cacerts 
-storepass changeit -file eleme.cer
其中,因为我们用的是JDK12,跟常用的JDK8目录是不一样的,所以我的cacerts目录是:/Users/ZHONGHUI/develop/jdk-12.0.2.jdk/Contents/Home/lib/security/cacerts,如果你用的是JDK1.8或者1.7的话,那么你的cacerts在jre下的 /lib/security/下。eleme是我们给复制过来的证书取得别名,eleme.cer就是我们下载改名后的证书。changeit 是密码,如果changeit不行的话,就试试changeme,一般是changeit。
生成keystore文件
keytool -import -storepass changeit -file eleme.cer -keystore eleme.keystore
然后就会生成一个eleme.keystore文件,若权限不够,加sudo即可。将它复制到项目的类路径下。
项目中配置
将下面的RestTemplateConfig替换原来的:
package com..xxx.config; 
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.client.RestTemplate; 
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
@Configuration
public class RestTemplateConfig { 
  @Autowired
  private FastJsonHttpMessageConverter httpMessageConverter;
  @Bean
  RestTemplate restTemplate() throws Exception {
  HttpComponentsClientHttpRequestFactory factory = new                          
      HttpComponentsClientHttpRequestFactory();
    factory.setConnectionRequestTimeout(5 * 60 * 1000);
    factory.setConnectTimeout(5 * 60 * 1000);
    factory.setReadTimeout(5 * 60 * 1000);
    // https
    SSLContextBuilder builder = new SSLContextBuilder();
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    ClassPathResource resource = new ClassPathResource("nonghang.keystore");
    InputStream inputStream = resource.getInputStream();
    keyStore.load(inputStream, null);
    SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", new PlainConnectionSocketFactory())
        .register("https", socketFactory).build();
    PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
    phccm.setMaxTotal(200);
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(phccm).setConnectionManagerShared(true).build();
    factory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(factory);
    List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
      ArrayList<HttpMessageConverter<?>> convertersValid = new ArrayList<>();
      for (HttpMessageConverter<?> converter : converters) {
        if (converter instanceof MappingJackson2HttpMessageConverter ||
          converter instanceof MappingJackson2XmlHttpMessageConverter) {
          continue;
        }
        convertersValid.add(converter);
      }
      convertersValid.add(httpMessageConverter);
      restTemplate.setMessageConverters(convertersValid);
    inputStream.close();
    return restTemplate;
  }
}
好啦,万事俱备只欠东风,自信满满的试了一下,发现还是行不通,深受打击的我决定还是去看看导致这类报错的原因。
从根源出发,https的socket工作原理是怎样的?
https的socket工作原理流程图如下:
DSC0002.jpg

那么,什么是SSL证书?
SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
首先,要知道导致报这个异常的原因不仅仅是因为证书校验不通过。
1.在我们通过https链接服务器时,服务器会给我们返回一个证书,这个证书可能经过CA认证,也可能是未认证的自制证书,客户端拿到这个证书后会对这个证书进行验证,如果是经过CA验证的证书,自然证书校验就能通过,自制证书自然就校验不同过,从而导致上边的异常。
2.证书校验通过后,还需要校验访问的域名是否和证书指定的域名是否匹配。未匹配也会导致如上异常。
3.上边两步都校验通过了才开始进行握手,但握手也有可能失败,从而导致上边的异常。
以上三个步骤中任何一个出了问题,都会连接失败。
首先,我们获取的证书是经过CA认证的,理应不会出现不通过的情况,第三方的域名也是没有问题的。那么到底是哪里出了问题呢?
后来我们从网络组那边了解到,他们那边对最近做了大的改动,之前的proxy都要重新加,加的时候把我们的给漏掉了。。。。辛苦大半天,被坑惨了啊。。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持CodeAE代码之家
原文链接:https://blog.csdn.net/weixin_42551921/article/details/118718678

关注下面的标签,发现更多相似文章