使用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認證的問題:

在網上找資料,解決問題有兩個思路:

1.通過相關配置,跳過SSL證書驗證,完成請求。

2.下載證書,將證書導入到JDK的證書管理裡面。

第一種方法相當於是取巧,逃避問題,我們當然不能那麼做瞭,我們要迎接挑戰,所以小編直接pass第一條,選擇第二條。

添加Https證書

下載證書

先通過瀏覽器將未簽名驗證的證書保存到本地, 具體步驟:

點擊 不安全–> 證書–> 詳細信息 –> 復制到文件 然後默認選擇 起一個文件名 , 保存即可。

(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工作原理流程圖如下:

那麼,什麼是SSL證書?

SSL 證書就是遵守 SSL協議,由受信任的數字證書頒發機構CA,在驗證服務器身份後頒發,具有服務器身份驗證和數據傳輸加密功能。

首先,要知道導致報這個異常的原因不僅僅是因為證書校驗不通過。

1.在我們通過https鏈接服務器時,服務器會給我們返回一個證書,這個證書可能經過CA認證,也可能是未認證的自制證書,客戶端拿到這個證書後會對這個證書進行驗證,如果是經過CA驗證的證書,自然證書校驗就能通過,自制證書自然就校驗不同過,從而導致上邊的異常。

2.證書校驗通過後,還需要校驗訪問的域名是否和證書指定的域名是否匹配。未匹配也會導致如上異常。

3.上邊兩步都校驗通過瞭才開始進行握手,但握手也有可能失敗,從而導致上邊的異常。

以上三個步驟中任何一個出瞭問題,都會連接失敗。

首先,我們獲取的證書是經過CA認證的,理應不會出現不通過的情況,第三方的域名也是沒有問題的。那麼到底是哪裡出瞭問題呢?

後來我們從網絡組那邊瞭解到,他們那邊對最近做瞭大的改動,之前的proxy都要重新加,加的時候把我們的給漏掉瞭。。。。辛苦大半天,被坑慘瞭啊。。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: