避免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);
    }

 

 

posted @ 2012-08-22 15:44  跳刀的兔子  阅读(78449)  评论(1编辑  收藏  举报