最近在用java請求內部的一個HTTP接口,URL是HTTPS加密形式的,大致的代碼是這樣的:
1 public String sendGet(String url) { 2 String result = ""; 3 BufferedReader in = null; 4 try { 5 String urlNameString = url ; 6 URL realUrl = new URL(urlNameString); 7 // 打開和URL之間的連接 8 URLConnection connection = realUrl.openConnection(); 9 // 設置通用的請求屬性 10 connection.setRequestProperty("accept", "*/*"); 11 connection.setRequestProperty("connection", "Keep-Alive"); 12 connection.setRequestProperty("user-agent", 13 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); 14 // 建立實際的連接 15 connection.connect(); 16 17 // 定義 BufferedReader輸入流來讀取URL的響應 18 in = new BufferedReader(new InputStreamReader( 19 connection.getInputStream(),"utf-8")); 20 String line; 21 while ((line = in.readLine()) != null) { 22 result += line; 23 } 24 } catch (Exception e) { 25 26 e.printStackTrace(); 27 } 28 // 使用finally塊來關閉輸入流 29 finally { 30 try { 31 if (in != null) { 32 in.close(); 33 } 34 } catch (Exception e2) { 35 e2.printStackTrace(); 36 } 37 } 38 return result; 39 }
從tomcat的日志來看,拋出的異常如下:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
從谷歌的一些搜索來看,基本都建議使用keytool導入CA證書,例如:
Resolving javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Error?
發現這種方法並沒有解決我的問題,而且HTTPS URL對應的證書是從GoDaddy購買的正規證書,應該不存在JRE不信任的問題。
直到搜索到這篇文章:解決PKIX:unable to find valid certification path to requested target 的問題
雖然方法大同小異,但通過執行編譯後的文件發現:並不是服務器真實的證書啊!
域名、證書都不是,第一反應:我的服務器被黑啦?趕緊Wireshark抓包,結果證明確實是服務器返回的證書,這就更奇怪了
由於HTTPS的接口是部署在CDN上的,所以趕緊聯系CDN廠商反饋問題,CDN廠商那邊反復確認他們那邊沒有問題,一切正常。
又開始懷疑自己是否遭到了SSL中間人攻擊,反復確認沒有問題。在排查的過程中發現兩個現象:
1、上圖中的域名對應的IP就是我們CDN廠商的IP,也就是說 和我們使用同一家CDN,浏覽器打開他們的網站,證書也完全吻合。
2、在一台WIN 2003上分別用FF和IE 7訪問HTTPS接口站點,FF的證書是正常的,IE7的證書和上圖中的是一致(即:錯誤的證書)。
基本可以判斷要麼CDN那邊返回錯了,要麼客戶端請求的時候有什麼東西發錯了。突然想到虛擬主機(一個IP同一個端口部署多個站點)的原理,就是根據HTTP HEADER中HOST參數來區分。那SSL怎麼實現不同的域名返回不同的證書呢?
直到發現了一個叫SNI(Server Name Indication)的概念,主要的作用是允許在相同的IP地址和TCP端口號的服務器上使用多個證書,而不必所有網站都使用同一個證書。在概念上等同於HTTP/1.1基於域名的虛擬主機,只不過這是在HTTPS上實現的。
更重要的是JRE從1.7版本才開始支持SNI,而我tomcat服務器上的JRE為1.6版本,不支持SNI。原因找到了,果斷升級到最新的1.8版本,重啟tomcat,問題立馬解決~