1、简介
在互联网安全通信方式上,目前用的最多的就是https配合ssl和数字证书来保证传输和认证安全了。
2、名词介绍
2.1 HTTP: HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。
2.2 HTTPS: HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
2.3 SSL (Secure Socket Layer)为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取及窃听。
3、Http与Https请求
本工具封装了采用HttpClient发送HTTP请求的方法,本工具所采用的是HttpComponents-Client-4.2.1,开发HTTPS应用时,时常会遇到两种情况
- 测试服务器没有有效的SSL证书,客户端连接时就会抛异常
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated - 测试服务器有SSL证书,但可能由于各种不知名的原因,它还是会抛一堆烂码七糟的异常,诸如下面这两种
javax.net.ssl.SSLException: hostname in certificate didn't match:
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
这里使用的是HttpComponents-Client-4.2.1创建的连接,所以就要告诉它使用一个不同的TrustManager,由于SSL使用的模式是X.509,对于该模式,Java有一个特定的TrustManager,称为X509TrustManager,TrustManager是一个用于检查给定的证书是否有效的类,所以我们自己创建一个X509TrustManager实例,而在X509TrustManager实例中,若证书无效,那么TrustManager在它的checkXXX()方法中将抛出CertificateException,既然我们要接受所有的证书,那么X509TrustManager里面的方法体中不抛出异常就行了,然后创建一个SSLContext并使用X509TrustManager实例来初始化之,接着通过SSLContext创建SSLSocketFactory,最后将SSLSocketFactory注册给HttpClient就可以了。
3.1 发送HTTP_GET请求
该方法会自动关闭连接,释放资源,方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串,请求参数含中文时,经测试可直接传入中文,HttpClient会自动编码发给Server,应用时应根据实际效果决定传入前是否转码,该方法会自动获取到响应消息头中[Content-Type:text/html; charset=GBK]的charset值作为响应报文的解码字符集,若响应消息头中无Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1作为响应报文的解码字符集
/**
* requestURL 请求地址(含参数)
* 远程主机响应正文
*/
public static String sendGetRequest(String reqURL, String encodeCharset) {
String respContent = "{status:\"0\",result:\"通信失败\"}"; // 响应内容
@SuppressWarnings("resource")
HttpClient httpClient = new DefaultHttpClient(); // 创建默认的httpClient实例
// 设置代理服务器
// httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,
// new HttpHost("10.0.0.4", 8080));
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT); // 连接超时20s
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT); // 读取超时50s
HttpGet httpGet = new HttpGet(reqURL); // 创建org.apache.http.client.methods.HttpGet
try {
HttpResponse response = httpClient.execute(httpGet); // 执行GET请求
HttpEntity entity = response.getEntity(); // 获取响应实体
if (null != entity) {
// respCharset=EntityUtils.getContentCharSet(entity)也可以获取响应编码,但从4.1.3开始不建议使用这种方式
respContent = EntityUtils.toString(entity, encodeCharset);
// Consume response content
EntityUtils.consume(entity);
}
System.out.println("-------------------------------------------------------------------------------------------");
StringBuilder respHeaderDatas = new StringBuilder();
for (Header header : response.getAllHeaders()) {
respHeaderDatas.append(header.toString()).append("\r\n");
}
String respStatusLine = response.getStatusLine().toString(); // HTTP应答状态行信息
String respHeaderMsg = respHeaderDatas.toString().trim(); // HTTP应答报文头信息
String respBodyMsg = respContent; // HTTP应答报文体信息
System.out.println("HTTP请求地址:" + reqURL);
System.out.println("HTTP应答完整报文\n" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n" + respBodyMsg + "");
//
System.out.println("-------------------------------------------------------------------------------------------");
} catch (ConnectTimeoutException cte) {
cte.printStackTrace();
// Should catch ConnectTimeoutException, and don`t catch
// org.apache.http.conn.HttpHostConnectException
// //LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
// cte);
} catch (SocketTimeoutException ste) {
ste.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",
// ste);
} catch (ClientProtocolException cpe) {
cpe.printStackTrace();
// 该异常通常是协议错误导致:比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合HTTP协议要求等
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时协议异常,堆栈轨迹如下",
// cpe);
} catch (ParseException pe) {
pe.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时解析异常,堆栈轨迹如下",
// pe);
} catch (IOException ioe) {
ioe.printStackTrace();
// 该异常通常是网络原因引起的,如HTTP服务器未启动等
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时网络异常,堆栈轨迹如下",
// ioe);
} catch (Exception e) {
e.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
} finally {
// 关闭连接,释放资源
httpClient.getConnectionManager().shutdown();
}
return respContent;
}
3.2 发送HTTP_POST请求
该方法允许自定义任何格式和内容的HTTP请求报文体,该方法会自动关闭连接,释放资源,方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串,请求参数含中文等特殊字符时,可直接传入本方法,并指明其编码字符集encodeCharset参数,方法内部会自动对其转码,该方法在解码响应报文时所采用的编码,取自响应消息头中的[Content-Type:text/html;charset=GBK]的charset值,若响应消息头中未指定Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1
/**
*
* @param reqURL请求地址
* @param reqData请求参数,若有多个参数则应拼接为param11=value11&22=value22&33=value33的形式
* @param encodeCharset编码字符集,编码请求数据时用之,此参数为必填项(不能为""或null)
*
*/
public static String sendPostRequest(String reqURL, String reqData, String encodeCharset, String contentType) {
String respContent = "通信失败";
@SuppressWarnings("resource")
HttpClient httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
HttpPost httpPost = new HttpPost(reqURL);
// 由于下面使用的是new
// StringEntity(....),所以默认发出去的请求报文头中CONTENT_TYPE值为text/plain;
// charset=ISO-8859-1
// 这就有可能会导致服务端接收不到POST过去的参数,比如运行在Tomcat6.0.36中的Servlet,所以我们手工指定CONTENT_TYPE头消息
httpPost.setHeader(HTTP.CONTENT_TYPE, contentType + "; charset=" + encodeCharset);
try {
httpPost.setEntity(new StringEntity(reqData == null ? "" : reqData, encodeCharset));
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (null != entity) {
respContent = EntityUtils.toString(entity, "utf-8");
EntityUtils.consume(entity);
}
System.out.println("-------------------------------------------------------------------------------------------");
StringBuilder respHeaderDatas = new StringBuilder();
for (Header header : response.getAllHeaders()) {
respHeaderDatas.append(header.toString()).append("\r\n");
}
String respStatusLine = response.getStatusLine().toString(); // HTTP应答状态行信息
String respHeaderMsg = respHeaderDatas.toString().trim(); // HTTP应答报文头信息
String respBodyMsg = respContent; // HTTP应答报文体信息
System.out.println("HTTP请求地址:" + reqURL);
System.out.println("HTTP请求参数:" + reqData);
System.out.println("HTTP请求方式:" + contentType);
System.out.println("HTTP应答完整报文\n" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n" + respBodyMsg + "");
//
System.out.println("-------------------------------------------------------------------------------------------");
} catch (ConnectTimeoutException cte) {
cte.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
// cte);
} catch (SocketTimeoutException ste) {
ste.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",
// ste);
} catch (Exception e) {
e.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
} finally {
httpClient.getConnectionManager().shutdown();
}
return respContent;
}
3.3 发送HTTP_POST_SSL请求
该方法会自动关闭连接,释放资源,该方法亦可处理普通的HTTP_POST请求,当处理HTTP_POST_SSL请求时,默认请求的是对方443端口,除非reqURL参数中指明了SSL端口,方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串,请求参数含中文等特殊字符时,可直接传入本方法,并指明其编码字符集encodeCharset参数,方法内部会自动对其转码,方法内部会自动注册443作为SSL端口,若实际使用中reqURL指定的SSL端口非443,可自行尝试更改方法内部注册的SSL端口该方法在解码响应报文时所采用的编码,取自响应消息头中的[Content-Type:text/html;charset=GBK]的charset值,若响应消息头中未指定Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1
/**
* @param reqURL 请求地址
* @param params 请求参数
* @param encodeCharset 编码字符集,编码请求数据时用之,当其为null时,则取HttpClient内部默认的ISO-8859-1编码请求参数
* @return 远程主机响应正文
*/
public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset) {
String responseContent = "通信失败";
@SuppressWarnings("resource")
HttpClient httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
// 创建TrustManager()
// 用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
X509TrustManager trustManager = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
// 创建HostnameVerifier
// 用于解决javax.net.ssl.SSLException: hostname in certificate didn't match:
// <123.125.97.66> != <123.125.97.241>
X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier() {
public void verify(String host, SSLSocket ssl) throws IOException {
}
public void verify(String host, X509Certificate cert) throws SSLException {
}
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
}
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
};
try {
// TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext
SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
// 使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用
sslContext.init(null, new TrustManager[] { trustManager }, null);
// 创建SSLSocketFactory
SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier);
// 通过SchemeRegistry将SSLSocketFactory注册到HttpClient上
httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
// 创建HttpPost
HttpPost httpPost = new HttpPost(reqURL);
// 由于下面使用的是new
// UrlEncodedFormEntity(....),所以这里不需要手工指定CONTENT_TYPE为application/x-www-form-urlencoded
// 因为在查看了HttpClient的源码后发现,UrlEncodedFormEntity所采用的默认CONTENT_TYPE就是application/x-www-form-urlencoded
// httpPost.setHeader(HTTP.CONTENT_TYPE,
// "application/x-www-form-urlencoded; charset=" + encodeCharset);
// 构建POST请求的表单参数
if (null != params) {
List<NameValuePair> formParams = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : params.entrySet()) {
formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset));
}
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (null != entity) {
responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
EntityUtils.consume(entity);
}
} catch (ConnectTimeoutException cte) {
cte.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
// cte);
} catch (SocketTimeoutException ste) {
ste.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",
// ste);
} catch (Exception e) {
e.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
} finally {
httpClient.getConnectionManager().shutdown();
}
return responseContent;
}
3.4 sendGetSSLRequest
/**
* @param reqURL
* @param params
* @param encodeCharset
* @return
*/
public static String sendGetSSLRequest(String reqURL, String encodeCharset) {
String responseContent = "通信失败";
@SuppressWarnings("resource")
HttpClient httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
// 创建TrustManager()
// 用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
X509TrustManager trustManager = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
// 创建HostnameVerifier
// 用于解决javax.net.ssl.SSLException: hostname in certificate didn't match:
// <123.125.97.66> != <123.125.97.241>
X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier() {
public void verify(String host, SSLSocket ssl) throws IOException {
}
public void verify(String host, X509Certificate cert) throws SSLException {
}
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
}
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
};
try {
// TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext
SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
// 使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用
sslContext.init(null, new TrustManager[] { trustManager }, null);
// 创建SSLSocketFactory
SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier);
// 通过SchemeRegistry将SSLSocketFactory注册到HttpClient上
httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
// 创建org.apache.http.client.methods.HttpGet
HttpGet httpGet = new HttpGet(reqURL);
// 由于下面使用的是new
// UrlEncodedFormEntity(....),所以这里不需要手工指定CONTENT_TYPE为application/x-www-form-urlencoded
// 因为在查看了HttpClient的源码后发现,UrlEncodedFormEntity所采用的默认CONTENT_TYPE就是application/x-www-form-urlencoded
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (null != entity) {
responseContent = EntityUtils.toString(entity, encodeCharset);
EntityUtils.consume(entity);
}
System.out.println(
"-------------------------------------------------------------------------------------------");
StringBuilder respHeaderDatas = new StringBuilder();
for (Header header : response.getAllHeaders()) {
respHeaderDatas.append(header.toString()).append("\r\n");
}
@SuppressWarnings("unused")
String respStatusLine = response.getStatusLine().toString(); // HTTP应答状态行信息
@SuppressWarnings("unused")
String respHeaderMsg = respHeaderDatas.toString().trim(); // HTTP应答报文头信息
@SuppressWarnings("unused")
String respBodyMsg = responseContent; // HTTP应答报文体信息
// System.out.println("HTTP应答完整报文=[" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n\r\n" + respBodyMsg + "]");
//
// System.out.println(
// "-------------------------------------------------------------------------------------------");
} catch (ConnectTimeoutException cte) {
cte.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
// cte);
} catch (SocketTimeoutException ste) {
ste.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",ste);
} catch (Exception e) {
e.printStackTrace();
// LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
} finally {
httpClient.getConnectionManager().shutdown();
}
return responseContent;
}
}
4、结束语
本文介绍到此结束,如果读者喜欢,您的喜欢与关注就是对我最大的支持,后续将写几篇Shiro的文章,刚刚做过的项目,希望大家喜欢,如果又不正确的地方,希望读者可以留言给我,我会及时更改!
网友评论