問題描述
使用HttpClient(版本4.5)進(jìn)行HTTPS請(qǐng)求時(shí),如果目標(biāo)主機(jī)和證書域名不一致時(shí)(比如在測(cè)試或開發(fā)環(huán)境中使用生產(chǎn)的證書或生成證書時(shí)未指定)會(huì)報(bào)錯(cuò):
javax.net.ssl.SSLPeerUnverifiedException: Certificate for <> doesn't match any of the subject alternative names: []
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:467)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:397)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:355)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:359)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:381)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
解決辦法
最好的解決辦法當(dāng)然是搞清楚為什么證書域名不匹配,是不是服務(wù)方給了你一個(gè)假證書。一般情況下,在生產(chǎn)環(huán)境上肯定是一致的,否則你的網(wǎng)站會(huì)被瀏覽器攔截。
為了開發(fā)調(diào)試順利進(jìn)行,我這里在代碼層面繞過了SSL域名驗(yàn)證:
SSLContext sslcontext = sslContext(keyStorePath, keyStorePassword);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
// 正常的SSL連接會(huì)驗(yàn)證碼所有證書信息
// .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
// 只忽略域名驗(yàn)證碼
.register("https", new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE)).build();
這里的 NoopHostnameVerifier.INSTANCE
該主機(jī)名驗(yàn)證器本質(zhì)上會(huì)關(guān)閉主機(jī)名驗(yàn)證。它接受任何有效的和符合目標(biāo)主機(jī)的SSL會(huì)話。
具體的示例代碼:
public static ClientResponse postSSL(String url, String resquestBody, String keyStorePath,
String keyStorePassword) {
SSLContext sslcontext = sslContext(keyStorePath, keyStorePassword);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
// 正常的SSL連接會(huì)驗(yàn)證碼所有證書信息
// .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
// 只忽略域名驗(yàn)證碼
.register("https", new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE)).build();
HttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
HttpClients.custom().setConnectionManager(connManager);
ClientResponse rsp = null;
try ( // 創(chuàng)建post方式請(qǐng)求對(duì)象
CloseableHttpClient client = HttpClients.custom().
setConnectionManager(connManager).build();) {
// 設(shè)置連接超時(shí)時(shí)間
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
.setConnectTimeout(CONNECT_TIME_OUT).setSocketTimeout(SOCKET_TIME_OUT).build();
// 創(chuàng)建httpclient對(duì)象
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
// 2 直接是拼接好的key=value或者json字符串等
httpPost.setEntity(new StringEntity(resquestBody, Const.CHARSET_UTF8));
// 執(zhí)行請(qǐng)求操作,并拿到結(jié)果
CloseableHttpResponse response = client.execute(httpPost);
rsp = new ClientResponse();
// 獲取響應(yīng)頭
Header[] rspHeaders = response.getAllHeaders();
if (ArrayUtils.isNotEmpty(rspHeaders)) {
Map<String, String> tmp = new HashMap<>();
for (Header header : rspHeaders) {
tmp.put(header.getName(), header.getValue());
}
}
// 響應(yīng)碼
rsp.setResponseCode(response.getStatusLine().getStatusCode());
// 獲取結(jié)果實(shí)體
HttpEntity entity = response.getEntity();
if (entity != null) {
/*
* 按指定編碼轉(zhuǎn)換結(jié)果實(shí)體為String類型。 如果這行報(bào)錯(cuò) connection
* reset,那么有可能是鏈路不通或者post的url過長(zhǎng)。
*/
String body = EntityUtils.toString(entity, Const.CHARSET_UTF8);
rsp.setResponseContent(body);
}
// 關(guān)閉流
EntityUtils.consume(entity);
// 釋放鏈接
response.close();
// 關(guān)閉客戶端
client.close();
} catch (Exception e) {
LOGGER.error("請(qǐng)求出錯(cuò)", e);
}
return rsp;
}
/**
* 設(shè)置信任自簽名證書
*
* @param keyStorePath
* 密鑰庫(kù)路徑
* @param keyStorepass
* 密鑰庫(kù)密碼
* @return
*/
public static SSLContext sslContext(String keyStorePath, String keyStorepass) {
SSLContext sc = null;
FileInputStream instream = null;
KeyStore trustStore = null;
try {
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// 加載密鑰庫(kù)
instream = new FileInputStream(new File(keyStorePath));
trustStore.load(instream, keyStorepass.toCharArray());
// 相信自己的CA和所有自簽名的證書
sc = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
} catch (Exception e) {
LOGGER.error("HTTPS請(qǐng)求初始化SSL異常", e);
} finally {
CloseUtil.close(instream);
}
return sc;
}