一、服务端不强制使用证书认证
可以使用httpclient提供的方法,绕过证书认证
private final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(TIMEOUT).setConnectTimeout(TIMEOUT).build();
private synchronized CloseableHttpClient getHttpsClient() {
if (null == httpsClient) {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
//关闭空闲两分钟的连接
cm.closeIdleConnections(120, TimeUnit.SECONDS);
SSLConnectionSocketFactory sslCSF = null;
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
sslCSF = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
httpsClient = HttpClients.custom().setSSLSocketFactory(sslCSF).setConnectionManager(cm).setDefaultRequestConfig(requestConfig).build();
}
return httpsClient;
}
二、服务端强制使用证书认证
第一种方法,将导出的证书导入jdk中:
keytool -import -keystore d:\Java\jdk1.7.0_60\jre\lib\security\cacerts -file d:/cas/keys/mycas.crt -alias anyname其中:d:\Java\jdk1.7.0_60\jre\lib\security\cacerts为客户端JVM的密钥库位置
如果提示:
keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect
那么输入密码:changeit
这是因为JDK安装后会默认创建一个密钥库,密码为:changeit
也可以删除d:\Java\jdk1.7.0_60\jre\lib\security\cacerts 再输入上述命令。
这时可以直接使用非ssl认证的httpclient:
/**
* @return CloseableHttpClient
* @throws
* @Description: 得到http client对象
*/
private synchronized CloseableHttpClient getHttpClient() {
if (null == httpClient) {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(30);
//关闭空闲两分钟的连接
cm.closeIdleConnections(120, TimeUnit.SECONDS);
httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig).build();
}
return httpClient;
}
第二种方法,自己创建密钥库:
1、证书相关问题。
生成客户端证书库: {jdk-path}/bin中有一个keytool,通过命令生成xxx.keystrore
keytool -genkey -keyalg RSA -validity 2000 -keystore xxx.keystrore
按照提示完成生成过程:
具体参数含义如下图:
现在只是生成了一个存放证书的”库”,我们需要把访问的server端的客户端证书导入到我们刚才创建的”库”中。
(具体每个浏览器导出证书的过程类似)
Chrome 导出证书过程:
具体导出的cer方式应该都是可以的,我是默认导出第一个格式的。这里需要注意的是,导出的这个证书域名很重要,这个要与你代码里的域名保持一致,否则会提示域名不匹配导致请求被拒绝。
现在我们需要将导出的证书导入到我们刚才生成的xxx.keystore中,命令如下
keytool -importcert -trustcacerts -file zzz.cer -keystore xxx.keystore
其中-zzz.cer 就是你刚才导出证书的文件名。
其中-xxx.keystore就是你刚才生成的”库”。
2、java代码实现
import java.io.*;
import java.security.KeyStore;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import com.ning.http.client.*;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
下面是创建httpclient的方法:
private final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(TIMEOUT).setConnectTimeout(TIMEOUT).build();
/**
* 创建证书认证httpclient
* @param keystoreClassPath 证书路径
* @param certPassword 证书密钥
* @return
*/
private synchronized CloseableHttpClient getHttpsClientSSLAuth(String keystoreClassPath, String certPassword) {
if (null == httpsClientSSLAuth) {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
//关闭空闲两分钟的连接
cm.closeIdleConnections(120, TimeUnit.SECONDS);
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
//KeyStore trustStore = KeyStore.getInstance("PKCS12");
InputStream in = RequestServiceImpl.class.getResourceAsStream(keystoreClassPath);
trustStore.load(in, certPassword.toCharArray());
in.close();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
httpsClientSSLAuth = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(cm).setDefaultRequestConfig(requestConfig).build();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return httpsClientSSLAuth;
}
(我用的是java7),代码中需要指定的文件路径就是刚才生成的xxx.keystore库,具体原理就是构造请求,注册https协议,加载keystore,发送请求~我本地测试是可以通过的,具体的post请求中还需要加入具体的请求参数。不过最基本的还是http、https这些底层的东西应该清晰,要不仅仅的是把别人的代码copy下,跑通了,再次遇到类似的问题或许优化时会很无奈… 并且httpclient 发布的4.3的api还是有些改动的,如果不知道最基本的原理,永远只能停留在copy的阶段…. 加油,共同进步~。