peer not authenticated的終極解決方案

1、前述html

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

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)

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

2、原因安全

  首先,要知道致使報這個異常的緣由不單單是由於證書校驗不經過。服務器

  都知道,在咱們經過https連接服務器時,服務器會給咱們返回一個證書,這個證書可能通過CA認證,也多是未認證的自制證書,客戶端拿到這個證書後會對這個證書進行驗證,若是是通過CA驗證的證書,天然證書校驗就能經過,自制證書天然就校驗不一樣過,從而致使上邊的異常。session

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

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

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

3、解決方法ui

  經過網上搜索咱們大部分都會找到相似以下的解決方案:

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,替換後,從新運行看看。

  若是以上這些還沒能解決,那我也是不知道了,能夠留言,我跟你一塊找答案^_^。

相關文章
相關標籤/搜索