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,替換後,從新運行看看。
若是以上這些還沒能解決,那我也是不知道了,能夠留言,我跟你一塊找答案^_^。