1. 程式人生 > >使用httpclient發起https請求時peer not authenticated,handshake_failure

使用httpclient發起https請求時peer not authenticated,handshake_failure

轉載地址:http://www.cnblogs.com/metoy/p/6238061.html

一、前述

  使用httpclient發起https請求時,可能會遇到如下異常:

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
    at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:399)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:143)

  網上搜索也能找到一大堆的解決方案,但大部分都類似,就是跳過證書的驗證,於是跟著稀裡糊塗的將程式碼拷貝下來使用,結果呢?有的能解決,有的依舊報這個錯誤。到底咋回事呢,接下來就說說這個問題的解決方案。

二、緣由

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

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

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

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

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

三、解決方法

  通過網上搜索我們大部分都會找到類似如下的解決方案:

複製程式碼
SSLContext sslContext;
    try {
        sslContext = SSLContext.getInstance("SSL");
        // set up a TrustManager that trusts everything
        try {
            sslContext.init(null,
                    new
TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted( X509Certificate[] certs, String authType) { } public void checkServerTrusted( X509Certificate[] certs, String authType) { } } }, new SecureRandom()); } catch (KeyManagementException e) { } SSLSocketFactory ssf = new SSLSocketFactory(sslContext,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
複製程式碼

  這個解決方案針對以上的三個步驟中的第一步,就是放棄對證書的校驗,但是第二部還可能有問題,要想徹底解決還需要跳過對域名的校驗。這裡給出最終的解決方案:

複製程式碼
try {
                SSLContext sslContext = SSLContext.getInstance("TLS");
                X509TrustManager tm = new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                };
                sslContext.init(null,new TrustManager[]{tm},null);
                sslSocketFactory = new SSLSocketFactory(sslContext,new X509HostnameVerifier(){
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }

                    @Override
                    public void verify(String host, SSLSocket ssl) throws IOException {

                    }

                    @Override
                    public void verify(String host, X509Certificate cert) throws SSLException {

                    }

                    @Override
                    public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {

                    }
                });
            } catch (GeneralSecurityException e) {
                log.error("create SSLSocketFactory error:{}",e);
            }
            return sslSocketFactory;
複製程式碼

  這裡不僅放棄對證書的校驗,也放棄對hostname的校驗,通過空實現X509HostnameVerifier類。

  以上這個解決方案就會徹底解決問題嗎?不一定,還有一個步驟就是握手的步驟也可能出問題,怎麼判斷是不是握手步驟出了問題呢?可以在程式碼裡做如下設定:

System.setProperty("javax.net.debug","ssl");

  做了以上設定後,就可以列印https建立連線的日誌了,如下:

複製程式碼
true
addingastrustedcert:
證書內容略去
triggerseedingofSecureRandom
doneseedingSecureRandom
executingrequestGETHTTP/1.1
Ignoringunavailableciphersuite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoringunavailableciphersuite:TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoringunavailableciphersuite:TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoringunsupportedciphersuite:TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoringunsupportedciphersuite:TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoringunsupportedciphersuite:TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoringunavailableciphersuite:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoringunsupportedciphersuite:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoringunavailableciphersuite:TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoringunsupportedciphersuite:TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoringunsupportedciphersuite:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
Ignoringunsupportedciphersuite:TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
Ignoringunavailableciphersuite:TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoringunavailableciphersuite:TLS_RSA_WITH_AES_256_CBC_SHA
Ignoringunsupportedciphersuite:TLS_RSA_WITH_AES_128_CBC_SHA256
Allowunsaferenegotiation:false
Allowlegacyhellomessages:true
Isinitialhandshake:true
Issecurerenegotiation:false
%%Nocachedclientsession
***ClientHello,TLSv1
……
main,WRITE:TLSv1Handshake,length=181
main,READ:TLSv1Alert,length=2
main,RECVTLSv1ALERT:fatal,handshake_failure
main,calledcloseSocket()
main,handlingexception:javax.net.ssl.SSLHandshakeException:Receivedfatalalert:handshake_failure
複製程式碼

  通過最後一個片語“handshake_failure”,你一定可以確定是握手失敗了。這一般是因為客戶端的加密機制太簡單,伺服器認為不安全,握手失敗。

  握手失敗解決方案就比較簡單,下載一個UnlimitedJCEPolicyJDK7.zip 。在http://www.oracle.com/technetwork/java/javase/downloads/index.html下載就好了。裡面包含了兩個jar。在你的/lib/security,替換後,重新執行看看。

如果還是沒有解決問題,就要確認下客戶端和服務端jdk不版本的問題,客戶端服務端的jdk版本最好一致 。