简介

本文使用的Apache HttpClient版本为4.5.6.

在使用Apache HttpClient执行https请求时,有时会遇到ssl相关的异常,这里介绍如何通过HttpClient执行https请求。

这里有2种方式:忽略ssl证书并信任任意连接 和 导入证书到秘钥库。

忽略ssl证书并信任任意连接

ssl初始化时需要证书管理器(TrustManager),我们这里使用了一个实现了X509TrustManager的证书管理器。它不做证书的验证,并信任任意的连接。
然后通过HttpClients的setSSLSocketFactory()设置SSLSocketFactory即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class HttpClientFactory {
	private final static CloseableHttpClient HTTP_CLIENT;
	static {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = sslConnectionSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainsf)
                .register("https", sslsf)
                .build();
        connManager = new PoolingHttpClientConnectionManager(registry);
        // 设置最大连接数
        connManager.setMaxTotal(1000);
        // 设置每个连接的路由数
        connManager.setDefaultMaxPerRoute(100);
        // 定时关闭无效的连接
        new IdleConnectionMonitorThread(connManager).start();

        HTTP_CLIENT = HttpClients.custom()
                .setUserAgent(userAgent)
                .setSSLSocketFactory(sslsf)
                .setConnectionManager(connManager)
                .build();
    }

    /**
     * 获取Http客户端连接对象
     *
     * @return Http客户端连接对象
     */
    public final static CloseableHttpClient getCloseableHttpClient() {
        return HTTP_CLIENT;
    }

	private static class HttpsTrustManager implements X509TrustManager {

	    @Override
	    public void checkClientTrusted(X509Certificate[] arg0, String arg1)
	            throws CertificateException {
	        // TODO Auto-generated method stub

	    }

	    @Override
	    public void checkServerTrusted(X509Certificate[] arg0, String arg1)
	            throws CertificateException {
	        // TODO Auto-generated method stub

	    }

	    @Override
	    public X509Certificate[] getAcceptedIssuers() {
	        return new X509Certificate[]{};
	    }

	}

	private static SSLConnectionSocketFactory sslConnectionSocketFactory() {
	    return new SSLConnectionSocketFactory(sslContext(),
	            SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
	}

	private static SSLContext sslContext() {
	    SSLContext ctx = null;
	    try {
	        ctx = SSLContexts.custom().useSSL().build();
	        ctx.init(null, new TrustManager[] { new HttpsTrustManager() }, new SecureRandom());
	    } catch (KeyManagementException | NoSuchAlgorithmException e) {
	        e.printStackTrace();
	    }
	    return ctx;
	}
}

然后通过getCloseableHttpClient()拿到CloseableHttpClient,就可以执行https请求了(与http请求一样)。
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String getPageContent(String url,String charset) {
    HttpGet httpGet = new HttpGet(url);
    HttpResponse response = null;
    try {
        response = getCloseableHttpClient().execute(httpGet);
        return EntityUtils.toString(response.getEntity(), charset == null ? "UTF-8" : charset);
    } catch (IOException e) {
        LOGGER.error("解析路径异常 url = " + url,e);
    } finally {
        close(httpGet, response);
    }
    return null;
}

导入证书到秘钥库

如果已经有证书,我们将证书导入秘钥库即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class HttpClientFactory {

    private static CloseableHttpClient client;

    public static HttpClient getHttpsClient() throws Exception {

        if (client != null) {
            return client;
        }
        SSLContext sslcontext = getSSLContext();
        SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        client = HttpClients.custom().setSSLSocketFactory(factory).build();

        return client;
    }

    public static void releaseInstance() {
        client = null;
    }

    private SSLContext getSSLContext() throws KeyStoreException, 
    NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException {
        KeyStore trustStore  = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream instream = new FileInputStream(new File("my.keystore"));
        try {
            trustStore.load(instream, "nopassword".toCharArray());
        } finally {
            instream.close();
        }
        return SSLContexts.custom()
                .loadTrustMaterial(trustStore)
                .build();
    }
}

其实这2种方式也只有创建SSLContext不同。参考文章也说了生产环境还是建议使用第2种。

文章参考:https://prasans.info/2014/06/making-https-call-using-apache-httpclient/

如果是使用Java的HttpClientConnection,则可以参考:http://www.devsumo.com/technotes/2014/01/java-trusting-https-server-certificates/