最近在用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,問題立馬解決~