问题描述
使用HttpClient(版本4.5)进行HTTPS请求时,如果目标主机和证书域名不一致时(比如在测试或开发环境中使用生产的证书或生成证书时未指定)会报错:
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)
解决办法
最好的解决办法当然是搞清楚为什么证书域名不匹配,是不是服务方给了你一个假证书。一般情况下,在生产环境上肯定是一致的,否则你的网站会被浏览器拦截。
为了开发调试顺利进行,我这里在代码层面绕过了SSL域名验证:
SSLContext sslcontext = sslContext(keyStorePath, keyStorePassword);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
// 正常的SSL连接会验证码所有证书信息
// .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
// 只忽略域名验证码
.register("https", new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE)).build();
这里的 NoopHostnameVerifier.INSTANCE
该主机名验证器本质上会关闭主机名验证。它接受任何有效的和符合目标主机的SSL会话。
具体的示例代码:
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连接会验证码所有证书信息
// .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
// 只忽略域名验证码
.register("https", new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE)).build();
HttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
HttpClients.custom().setConnectionManager(connManager);
ClientResponse rsp = null;
try ( // 创建post方式请求对象
CloseableHttpClient client = HttpClients.custom().
setConnectionManager(connManager).build();) {
// 设置连接超时时间
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
.setConnectTimeout(CONNECT_TIME_OUT).setSocketTimeout(SOCKET_TIME_OUT).build();
// 创建httpclient对象
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
// 2 直接是拼接好的key=value或者json字符串等
httpPost.setEntity(new StringEntity(resquestBody, Const.CHARSET_UTF8));
// 执行请求操作,并拿到结果
CloseableHttpResponse response = client.execute(httpPost);
rsp = new ClientResponse();
// 获取响应头
Header[] rspHeaders = response.getAllHeaders();
if (ArrayUtils.isNotEmpty(rspHeaders)) {
Map<String, String> tmp = new HashMap<>();
for (Header header : rspHeaders) {
tmp.put(header.getName(), header.getValue());
}
}
// 响应码
rsp.setResponseCode(response.getStatusLine().getStatusCode());
// 获取结果实体
HttpEntity entity = response.getEntity();
if (entity != null) {
/*
* 按指定编码转换结果实体为String类型。 如果这行报错 connection
* reset,那么有可能是链路不通或者post的url过长。
*/
String body = EntityUtils.toString(entity, Const.CHARSET_UTF8);
rsp.setResponseContent(body);
}
// 关闭流
EntityUtils.consume(entity);
// 释放链接
response.close();
// 关闭客户端
client.close();
} catch (Exception e) {
LOGGER.error("请求出错", e);
}
return rsp;
}
/**
* 设置信任自签名证书
*
* @param keyStorePath
* 密钥库路径
* @param keyStorepass
* 密钥库密码
* @return
*/
public static SSLContext sslContext(String keyStorePath, String keyStorepass) {
SSLContext sc = null;
FileInputStream instream = null;
KeyStore trustStore = null;
try {
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// 加载密钥库
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请求初始化SSL异常", e);
} finally {
CloseUtil.close(instream);
}
return sc;
}