httpsurlconnection 无报错提示建立连接
java使用HttpsUrlConnection下载一个远程文件时,常规的方法会出现不信任证书的异常,解决的方法就是:使用自信认管理器,示例如下:
Java中忽略對建立SSL連線之certificate的檢查
建立SSL連線時,伺服器會提供一份電子憑證(certificate),以供用戶端程式在連線時用以編解與伺服器之間的訊息交換。正常的情況下,在建立連線之際,用戶端程式收到伺服器提供的ceritificate,會向這張ceritifccate的憑證授權單位(CA)要求檢驗此張憑證的有效性。
有許多網站,尤其是還在測試中的網站,因為向憑證授權單位購買憑證是需要花錢的,所以都會自行利用一些簽證的工具,自行產生憑證,一般來說,就被稱為是 self-signed的certificate。當我們透過像IE這樣子的瀏覽器來檢視網址為https://開頭的網站時,倘若看到下述的視窗:
通常都是遇上self signed的網站。瀏覽器處理這種情況很簡單,因為它可以詢問使用者是否願意接受,再進行下一步的處理。但是使用Java來連線此類的網站時,事情就比較複雜。倘若我們寫下下述的程式碼:
import java.io.*; import java.net.*;
public class TestCert { public static void main(String argv[]) { try { String _url = "https://www.cs.nthu.edu.tw/"; URL url = new URL(_url); URLConnection uc = url.openConnection(); InputStream is = uc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String s = br.readLine(); System.out.println(s); br.close(); isr.close(); } catch(Exception e) { e.printStackTrace(); } } } |
這段程式碼基本上就是連接到一個https://的網站,但由於這個網站的憑證是self signed的,所以發生了下述的錯誤:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderExce ption: unable to find valid certification path to requested target at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1 518) |
這便是Java的SSL socket實作在收到伺服器回傳的憑證時,由於無法認證該憑證,而引發的錯誤。有一個解決的方法,便是把這個憑證加到你Java執行環境中儲存信任憑證的地方,不過這不是本篇blog的主旨,本篇的主旨在於如何忽略檢查,方法如下:
import javax.net.ssl.*;
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { }
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { } }}; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); |
基本上,就是為目前SSL的SocketFactory設定一個自訂的TrustManager,並且在TrustManager該有的介面中,pass所有的檢查。如此一來,便可以忽略掉上述所提到的錯誤。
使用上述的方式,必須小心整個application是否會連線到不在預期中的網站,因為這樣子的寫法,等於是開放完全不檢查來自所有網站的憑證是否有效。我是偷懶的人,所以不想把憑證加到信任的cert store去,但因為明確的知道憑證的來源網站,所以才使用忽略的方式,倘若你的情況並非如此,就應該審慎的評估。
另外,如果你所收到的憑證,其中的domain name並未和其實際的domain name相符(我有遇到domain name填IP address的情況:p),也會引發憑證的檢查錯誤,解決之道(其實就是忽略之道)如下:
HostnameVerifier hv = new HostnameVerifier() { public boolean verify(String urlHostName, SSLSession session) { System.out.println("Warning: URL Host: "+urlHostName+" vs. "+session.getPeerHost()); return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hv); |
為java.net.ssl.HttpsURLConnection設定一個自訂的HostnameVerifier,並且在其verify()中回傳檢查成功(true),就可以把對憑證host name的檢查予以忽略。
以上兩種情況,約略是連接至測試用之https網站的主要問題。