这两天在做与渠道联调的回调,其中回调渠道的时候使用的是https。
简单的测试代码如下;
public static void simpleTest(String httpsURL) throws Exception {
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection) myurl.openConnection();
InputStream ins = con.getInputStream();
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader in = new BufferedReader(isr);
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
}
但是,如果为自签发的SSL证书(未提供证书发行链而不被信任)的请求地址,上面的代码就不起作用了。
错误为:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target。
经过查找资料,现有两种解决方案:
1)为自身系统增加证书
2)绕过证书验证
先来说第一种方案:
1)用IE打开访问的https网址,点击地址栏后面的小锁头(或证书错误标识),查看证书。在弹出的证书信息框中选择第二个页签“详细信息”。点击复制到文件,选择Base64编码的X.509证书导出即可。
2)把证书从其它文件导入到TrustStore文件中。
keytool -import -file D:/test1.cer -keystore D:/crt_test1
执行命令后要求输入密码,记住输入的密码。
3)编写如下代码:
public static void certTest1()throws Exception{
String httpsURL = "https://xxx.xxx.cn/";
String trustStor="D:/crt_test1";
System.setProperty("javax.net.ssl.trustStore", trustStor);
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection) myurl.openConnection();
con.setHostnameVerifier(hv);
InputStream ins = con.getInputStream();
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader in = new BufferedReader(isr);
String inputLine=null;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
}
private static HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return urlHostName.equals(session.getPeerHost());
}
};
可是如果出现两个https地址并且证书不同的话,上面的代码就没有办法处理了。有人可能说用System.setProperty重新设置一下trustStore,很抱歉,我试了好几次,没有成功。如果他人有成功的请给出代码,谢谢。
为了处理两个证书的情况,我又查了下资料,整理一下,使用TrustManager来处理(此时会用到证书的密码)。
代码如下:
public static void certTest2(String certDir, String passwd, String urlStr)
throws Exception {
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
TrustManager[] tms = getTms(certDir, passwd);
sslContext.init(null, tms, new java.security.SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(urlStr);
HttpsURLConnection.setDefaultHostnameVerifier(hv);
HttpsURLConnection conn = ((HttpsURLConnection) url.openConnection());
conn.setSSLSocketFactory(ssf);
InputStreamReader im = new InputStreamReader(conn.getInputStream(),
"GBK");
BufferedReader reader = new BufferedReader(im);
StringBuffer sb = new StringBuffer();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\r\n");
}
System.out.println(sb);
}
public static TrustManager[] getTms(String dir, String keyPassword)
throws Exception {
String talg = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmFact = TrustManagerFactory.getInstance(talg);
FileInputStream tfis = new FileInputStream(dir);
KeyStore ts = KeyStore.getInstance("jks");
ts.load(tfis, keyPassword.toCharArray());
tfis.close();
tmFact.init(ts);
return tmFact.getTrustManagers();
}
private static HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return urlHostName.equals(session.getPeerHost());
}
};
再说第二种方案:
不多说,直接上代码
public static void withoutCertTest(String urlStr) throws Exception {
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
TrustManager[] tms = { ignoreCertificationTrustManger };
sslContext.init(null, tms, new java.security.SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(urlStr);
HttpsURLConnection.setDefaultHostnameVerifier(hv);
HttpsURLConnection conn = ((HttpsURLConnection) url.openConnection());
conn.setSSLSocketFactory(ssf);
InputStreamReader im = new InputStreamReader(conn.getInputStream(),
"GBK");
BufferedReader reader = new BufferedReader(im);
StringBuffer sb = new StringBuffer();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\r\n");
}
System.out.println(sb);
}
private static TrustManager ignoreCertificationTrustManger = new X509TrustManager() {
private X509Certificate[] certificates;
@Override
public void checkClientTrusted(X509Certificate certificates[],
String authType) throws CertificateException {
if (this.certificates == null) {
this.certificates = certificates;
}
}
@Override
public void checkServerTrusted(X509Certificate[] ax509certificate,
String s) throws CertificateException {
if (this.certificates == null) {
this.certificates = ax509certificate;
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
参考网址:
http://guoguanfei.blog.163.com/blog/static/555830372009217115420766/
http://blog.youkuaiyun.com/c_4818/article/details/7825388
http://www.wenhq.com/article/view_711.html
===========================================================
以上测试以后,与渠道调用的时候并没有成功,于是使用apache提供的HttpClients,问题解决。
代码如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
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.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
/**
* HttpClientUtils, 使用 HttpClient 4.x<br>
*
*/
public class HttpClientUtils {
private static HttpClient client = null;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(128);
cm.setDefaultMaxPerRoute(128);
client = HttpClients.custom().setConnectionManager(cm).build();
}
/**
* 发送一个 Post 请求, 使用指定的字符集编码.
*
* @param url
* @param body
* RequestBody
* @param mimeType
* 例如 application/xml
* @param charset
* 编码
* @param connTimeout
* 建立链接超时时间,毫秒.
* @param readTimeout
* 响应超时时间,毫秒.
* @return ResponseBody, 使用指定的字符集编码.
*
* @throws ConnectTimeoutException
* 建立链接超时异常
* @throws SocketTimeoutException
* 响应超时
* @throws Exception
*/
public static String post(String url, String body, String mimeType,
String charset, Integer connTimeout, Integer readTimeout)
throws ConnectTimeoutException, SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
String result = "";
try {
if (StringUtils.isNotBlank(body)) {
HttpEntity entity = new StringEntity(body, ContentType.create(
mimeType, charset));
post.setEntity(entity);
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(post);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null
&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 提交form表单
*
* @param url
* @param params
* @param connTimeout
* @param readTimeout
* @return
* @throws ConnectTimeoutException
* @throws SocketTimeoutException
* @throws Exception
*/
public static String postForm(String url, Map<String, String> params,
Map<String, String> headers, Integer connTimeout,
Integer readTimeout) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
try {
if (params != null && !params.isEmpty()) {
List<NameValuePair> formParams = new ArrayList<org.apache.http.NameValuePair>();
Set<Entry<String, String>> entrySet = params.entrySet();
for (Entry<String, String> entry : entrySet) {
formParams.add(new BasicNameValuePair(entry.getKey(), entry
.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
formParams, Consts.UTF_8);
post.setEntity(entity);
}
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entry : headers.entrySet()) {
post.addHeader(entry.getKey(), entry.getValue());
}
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res = null;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(post);
}
return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null
&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
}
/**
* 发送一个 GET 请求
*
* @param url
* @param charset
* @return
* @throws Exception
*/
public static String get(String url, String charset) throws Exception {
return get(url, charset, null, null);
}
/**
* 发送一个 GET 请求
*
* @param url
* @param charset
* @param connTimeout
* 建立链接超时时间,毫秒.
* @param readTimeout
* 响应超时时间,毫秒.
* @return
* @throws ConnectTimeoutException
* 建立链接超时
* @throws SocketTimeoutException
* 响应超时
* @throws Exception
*/
public static String get(String url, String charset, Integer connTimeout,
Integer readTimeout) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
HttpClient client = null;
HttpGet get = new HttpGet(url);
String result = "";
try {
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
get.setConfig(customReqConf.build());
HttpResponse res = null;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(get);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(get);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
get.releaseConnection();
if (url.startsWith("https") && client != null
&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 从 response 里获取 charset
*
* @param ressponse
* @return
*/
@SuppressWarnings("unused")
private static String getCharsetFromResponse(HttpResponse ressponse) {
// Content-Type:text/html; charset=GBK
if (ressponse.getEntity() != null
&& ressponse.getEntity().getContentType() != null
&& ressponse.getEntity().getContentType().getValue() != null) {
String contentType = ressponse.getEntity().getContentType()
.getValue();
if (contentType.contains("charset=")) {
return contentType
.substring(contentType.indexOf("charset=") + 8);
}
}
return null;
}
private static CloseableHttpClient createSSLInsecureClient()
throws GeneralSecurityException {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(
null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext, new X509HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
@Override
public void verify(String host, SSLSocket ssl)
throws IOException {
}
@Override
public void verify(String host, X509Certificate cert)
throws SSLException {
}
@Override
public void verify(String host, String[] cns,
String[] subjectAlts) throws SSLException {
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (GeneralSecurityException e) {
throw e;
}
}
public static void main(String[] args) {
try {
String xml = IOUtils.toString(new FileInputStream(new File(
"D:\\hongkangtest.txt")));
System.out
.println(post(
"https://trade.tongbanjie.com/insurance/callback/hk/redeem.htm",
xml, "html/text", "GBK", 10000, 10000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar