爲了儘量避免安全問題,公司的不少系統服務都逐步https化,雖然開始過程會遇到各類問題,但趨勢不改。最完美的https應用是能實現雙向認證,客戶端用私鑰簽名用服務端公鑰加密,服務端用私鑰簽名客戶端都公鑰加密,但現實不少狀況不可能讓每一個客戶端都申請一個證書,所以只實現https的單項認證,即只要服務端又證書,客戶端只驗證https端證書可靠就可進行https通訊。在某些狀況下爲了避免花錢買第三方信任機構頒發都證書,客戶端在一些狀況下也不作服務器端都認證,兩邊只實現htts的加密通訊。最近就遇到一個問題,https調用證書驗證失敗,最終考慮仍是忽略調服務證書的驗證。html
java程序在訪問https資源時,出現報錯sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target這本質上,是java在訪問https資源時的證書信任問題。如何解決這個問題呢?爲什麼有這個問題?解決這個問題前,要了解1)https通訊過程客戶端在使用HTTPS方式與Web服務器通訊時有如下幾個步驟,如圖所示。(1)客戶使用https的URL訪問Web服務器,要求與Web服務器創建SSL鏈接。(2)Web服務器收到客戶端請求後,會將網站的證書信息(證書中包含公鑰)傳送一份給客戶端。(3)客戶端的瀏覽器與Web服務器開始協商SSL鏈接的安全等級,也就是信息加密的等級。(4)客戶端的瀏覽器根據雙方贊成的安全等級,創建會話密鑰,而後利用網站的公鑰將會話密鑰加密,並傳送給網站。(5)Web服務器利用本身的私鑰解密出會話密鑰。(6)Web服務器利用會話密鑰加密與客戶端之間的通訊。
2)java程序的證書信任規則如上文所述,客戶端會從服務端拿到證書信息。調用端(客戶端)會有一個證書信任列表,拿到證書信息後,會判斷該證書是否可信任。若是是用瀏覽器訪問https資源,發現證書不可信任,通常會彈框告訴用戶,對方的證書不可信任,是否繼續之類。Java虛擬機並不直接使用操做系統的keyring,而是有本身的security manager。與操做系統相似,jdk的security manager默認有一堆的根證書信任。若是你的https站點證書是花錢申請的,被這些根證書所信任,那使用java來訪問此https站點會很是方便。所以,若是用java訪問https資源,發現證書不可信任,則會報文章開頭說到的錯誤。解決問題的方法1)將證書導入到jdk的信任證書中(理論上應該可行,未驗證)2)在客戶端(調用端)添加邏輯,忽略證書信任問題第一種方法,須要在每臺運行該java程序的機器上,都作導入操做,不方便部署,所以,採用第二種方法。下面貼下該方法對應的代碼。驗證可行的代碼1)先實現驗證方法
- HostnameVerifier hv = new HostnameVerifier() {
- public boolean verify(String urlHostName, SSLSession session) {
- System.out.println("Warning: URL Host: " + urlHostName + " vs. "
- + session.getPeerHost());
- return true;
- }
- };
- private static void trustAllHttpsCertificates() throws Exception {
- javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
- javax.net.ssl.TrustManager tm = new miTM();
- trustAllCerts[0] = tm;
- javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
- .getInstance("SSL");
- sc.init(null, trustAllCerts, null);
- javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
- .getSocketFactory());
- }
- static class miTM implements javax.net.ssl.TrustManager,
- javax.net.ssl.X509TrustManager {
- public java.security.cert.X509Certificate[] getAcceptedIssuers() {
- return null;
- }
- public boolean isServerTrusted(
- java.security.cert.X509Certificate[] certs) {
- return true;
- }
- public boolean isClientTrusted(
- java.security.cert.X509Certificate[] certs) {
- return true;
- }
- public void checkServerTrusted(
- java.security.cert.X509Certificate[] certs, String authType)
- throws java.security.cert.CertificateException {
- return;
- }
- public void checkClientTrusted(
- java.security.cert.X509Certificate[] certs, String authType)
- throws java.security.cert.CertificateException {
- return;
- }
- }
2)在訪問https資源前,調用
- trustAllHttpsCertificates();
- HttpsURLConnection.setDefaultHostnameVerifier(hv);
http://blog.csdn.net/lizeyang/article/details/18983843java
解決https證書驗證不經過的問題
一、報錯信息api
java.security.cert.CertificateException: No name matching api.weibo.com found; nested exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching api.weibo.com found緣由:在調用api.weibo.com的時候,咱們使用的是https的方式,正常狀況下應該是使用api.weibo.com的證書,但因爲某些緣由,咱們只能使用本身的證書,致使在驗證證書的時候,就報了這個錯誤。瀏覽器
解決的辦法:忽略服務端和客戶端的證書校驗便可。java 提供的相關的類。tomcat
二、具體實現方式安全
經過重寫TrustManager的checkClientTrusted(檢查客戶端證書信任)和checkServerTrusted(檢查服務端證書驗證)。服務器
以及HostnameVerifier的verify(校驗)方法便可取消對證書的全部驗證。session
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.*; import java.io.IOException; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public final class DisableSSLCertificateCheckUtil { private static final Logger LOGGER = LoggerFactory.getLogger(DisableSSLCertificateCheckUtil.class); /** * Prevent instantiation of utility class. */ private DisableSSLCertificateCheckUtil() { } /** * Disable trust checks for SSL connections. */ public static void disableChecks() { try { new URL("https://0.0.0.0/").getContent(); } catch (IOException e) { // This invocation will always fail, but it will register the // default SSL provider to the URL class. } try { SSLContext sslc; sslc = SSLContext.getInstance("TLS"); TrustManager[] trustManagerArray = {new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }}; sslc.init(null, trustManagerArray, null); HttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }); } catch (Exception e) { LOGGER.error("error msg:{}", e); throw new IllegalArgumentException("證書校驗異常!"); } } }調用方式:ide
DisableSSLCertificateCheckUtil.disableChecks();影響的範圍:將會影響整個tomcat裏面對證書的驗證。即經過tomcat裏面的其餘項目雖然沒有執行這一段代碼可是也一樣會忽略證書的驗證。post
影響的時間:執行這段代碼以後的全部時間都生效。