版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sarafina527/article/details/89451676
目录
扫描二维码关注公众号,回复:
6169191 查看本文章
0.HTTPS
HTTPS与HTTP的区别就是基于SSL层,而不是直接基于TCP,
HTTPS VS HTTP区别
- 建立连接过程,HTTP的话几乎在TCP建连(3次握手)之后就可以传输应用数据了,但是HTTPS在TCP建连后,需要在SSL层经历SSL握手才能建立好SSL连接!
- 传输过程中,HTTP的报文就直接转字节流传递给TCP去发送了,而HTTPS的报文经过了SSL层的加密,对端经过解密才能到达对方的http层,才能根据http协议去解析字节流。
好奇SSL握手过程的请见https://mp.csdn.net/postedit/89333536
下面是JAVA的服务端和客户端实现
1.单向认证
使用一个最简单的say hello 的SpringBoot项目,修改配置文件application.properties支持https,
1.1 服务端
开启ssl,握手服务端发送sslTestServer.jks证书库中的名为www.server.com的服务端证书,证书库的密码是123456,证书类型为JKS
server.port=8085
# 私钥证书库 服务端证书
server.ssl.key-store=sslTestServer.jks
# 指定服务端证书 别名
server.ssl.key-alias=www.server.com
# 开启ssl
server.ssl.enabled=true
# 证书库密码
server.ssl.key-store-password=123456
# 证书库类型
server.ssl.key-store-type=JKS
并将sslTestServer.jks服务端证书库放在SpringBoot根目录下 ,证书文件有一个私钥www.server.com和一个公钥信任证书
1.2 客户端curl
curl https://localhost:8085/hello
访问结果可见,curl作为http客户端,默认使用单向认证,会认证服务端证书,此时会报错,因为服务端证书是个自签名的证书,而客户端携带特定的信任证书,所以无法构成证书链完成校验,所以在ssl层握手失败报错
curl -k https://localhost:8085/hello
使用提示的-k选项,就可以跳过服务端证书校验
1.3 浏览器
使用浏览器就会看见熟悉的页面,也是因为浏览器校验服务端证书不通过,无法认证是对应的网站,如果点击高级-继续前往,与curl -k 效果一样,信任服务端,不校验证书
1.4 Java httpClient实现
package communication.http.httpClinet;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
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.BasicHeader;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.File;
/**
* 利用HttpClient进行post请求的工具类
* 单向认证
*
*/
public class HttpClientUtil {
@SuppressWarnings("resource")
public static String doPost(String url,String jsonstr,String charset) throws Exception{
// 加载自定义的keystore
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new File("/Users/wangying49/certs/sslTestClient.jks"), "123456".toCharArray()).build();
// SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new File("/Users/wangying49/certs/ceb0305.jks"), "123456".toCharArray()).build();
// 默认的域名校验类为DefaultHostnameVerifier,
// 比对服务器证书的AlternativeName和CN两个属性。
SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
// 如果服务器证书CN和域名不一致,又必须让其校验通过,则可以自己实现HostnameVerifier。
public boolean verify(String s, SSLSession sslSession) {
// 重写域名校验逻辑
return true;
}
});
// 一个httpClient对象对于https仅会选用一个SSLSocketFactory
// 至少在4.5.3和4.5.4中,如果给HttpClient对象设置ConnectionManager,我们必须在PoolingHttpClientConnectionManager的构造方法中传入Registry,
// 并将https对应的工厂设置为我们自己的SSLConnectionSocketFactory对象,因为在DefaultHttpClientConnectionOperator.connect()中,逻辑是从这里找SSLConnectionSocketFactory的。
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("myssl", ssf)
.build());
connectionManager.setMaxTotal(20);
connectionManager.setDefaultMaxPerRoute(20);
// 不在connectionManager中注册,仅在这里设置ssf是无效的,详见build()内部逻辑,
// 在connectionManager不为null时,不会使用自定义的ssf
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(ssf)
.setConnectionManager(connectionManager)
.build();
HttpPost httpPost = null;
String result = null;
try{
httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type", "text/xml");
StringEntity se = new StringEntity(jsonstr);
se.setContentType("text/xml");
se.setContentEncoding(new BasicHeader("Content-Type", "text/xml"));
httpPost.setEntity(se);
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
result = EntityUtils.toString(resEntity,charset);
}
}
}catch(Exception ex){
ex.printStackTrace();
}
return result;
}
public static void main(String[] args) throws Exception{
String url = "myssl://localhost:8085/hello";
String jsonStr = "<xml></html>";
String rtnCode = HttpClientUtil.doPost(url, jsonStr, "utf-8");
System.out.println("响应结果:" + rtnCode);
}
}
SSLContexts.custom().loadTrustMaterial()这个方法,加载了服务端的信任用于认证服务端证书
2 双向认证
2.1 服务端
application.properties配置文件中添加一下,
# 信任证书库,此处将服务端私钥及证书、客户端的信任证书放在一个证书库里,所以与上面的相同
server.ssl.trust-store=sslTestServer.jks
# 信任证书库密码
server.ssl.trust-store-password=123456
# 开启客户端认证
server.ssl.client-auth=need
# 信任证书库类型
server.ssl.trust-store-type=JKS
server.ssl.trust-store-provider=SUN
2.2 客户端 curl
2.2.1 永久信任curl
curl -k https://localhost:8085/hello
报错!客户端认证失败
2.2.2 curl携带客户端证书
curl -k -E ./sslTestClient.p12:123456 https://localhost:8085/hello
-E选项通过带上客户端证书,里面包含客户端私钥和公钥证书(如下图),结果可见 可以正常访问。
2.2.3 Java客户端实现
File keyStoreFile = new File("/Users/wangying49/certs/sslTestClient.jks");
KeyStore ks = KeyStore.getInstance("JKS");
char[] ksPass = "123456".toCharArray();
ks.load(new FileInputStream(keyStoreFile), ksPass);
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(keyStoreFile, ksPass).loadKeyMaterial(ks, ksPass) .build();
主要是添加了客户端 密钥库loadKeyMaterial(ks, ksPass)即可实现。
由此可见通信两端的证书都用到了