避免HttpClient的”javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated”異常


在開發https應用時,你的測試服務器常常沒有一個(有效的)SSL證書。在你的客戶端連接測試服務器時,如下的異常會被拋出:”javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated”。

我將討論使用Apache HttpClient時,解決該問題的一種方法(http://hc.apache.org/httpcomponents-client/)。

 

1. 代碼片段

通常,你會像下面那樣來創建HttpClient:

this.client = new DefaultHttpClient();

我們將需要告訴client使用一個不同的TrustManager。TrustManager(http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/javax/net/ssl/TrustManager.html)是一個檢查給定的證書是否有效的類。SSL使用的模式是X.509(http://en.wikipedia.org/wiki/X.509),對於該模式Java有一個特定的TrustManager,稱為X509TrustManager。首先我們需要創建這樣的TrustManager。

X509TrustManager tm = new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
                    public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
                };

可以看到,該代碼做的不多:如果一個證書是無效的,那么TrustManager在checkXXX方法中將拋出CertificateException異常。既然我們總是接受所有的證書,我們從來不拋出異常。

下面我們需要找到一個將TrustManager設置到我們的HttpClient的方法。TrustManager只是被SSL的Socket所使用。Socket通過SocketFactory創建。對於SSL Socket,有一個SSLSocketFactory(http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/javax/net/ssl/SSLSocketFactory.html)。當創建新的SSLSocketFactory時,你需要傳入SSLContext到它的構造方法中。在SSLContext中,我們將包含我們新創建的TrustManager。

首先我們需要得到一個SSLContext:

SSLContext ctx = SSLContext.getInstance("TLS");

TLS是SSL的繼承者,但是它們使用相同的SSLContext。

然后我們需要使用我們上面新創建的TrustManager來初始化該上下文:

ctx.init(null, new TrustManager[]{tm}, null);

最后我們創建SSLSocketFactory:

SSLSocketFactory ssf = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

現在我們仍然需要將SSLSocketFactory注冊到我們的HttpClient上。這是在SchemeRegistry中完成的:

SchemeRegistry registry = new SchemeRegistry();
                registry.register(new Scheme("https", 443, ssf));
                ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(registry);
                return new DefaultHttpClient(mgr, base.getParams());

我們注冊了一個新的Scheme,使用協議https,我們新創建的SSLSocketFactory包含了我們的TrustManager,然后我們告訴HttpClienthttps的默認端口是443.

2. 完整示例代碼

下面的類接收HttpClient作為參數,然后返回一個新的接受任意SSL證書的HttpClient:

/**
     * 避免HttpClient的”SSLPeerUnverifiedException: peer not authenticated”異常
     * 不用導入SSL證書
     * @author shipengzhi(shipengzhi@sogou-inc.com)
     *
     */
    public static class WebClientDevWrapper {

        public static org.apache.http.client.HttpClient wrapClient(org.apache.http.client.HttpClient base) {
            try {
                SSLContext ctx = SSLContext.getInstance("TLS");
                X509TrustManager tm = new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
                    public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
                };
                ctx.init(null, new TrustManager[] { tm }, null);
                SSLSocketFactory ssf = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                SchemeRegistry registry = new SchemeRegistry();
                registry.register(new Scheme("https", 443, ssf));
                ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(registry);
                return new DefaultHttpClient(mgr, base.getParams());
            } catch (Exception ex) {
                ex.printStackTrace();
                return null;
            }
        }
    }

現在你就可以在代碼中如下的創建HttpClient了:

public HttpClient4() {
        this.client = new DefaultHttpClient();
        this.client = WebClientDevWrapper.wrapClient(client);
    }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM