https 客户端-服务端 实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sarafina527/article/details/89451676

目录

0.HTTPS

HTTPS VS HTTP区别

1.单向认证

1.1 服务端

1.2 客户端curl

1.3 浏览器

1.4 Java httpClient实现

2 双向认证

2.1 服务端

2.2 客户端 curl

2.2.1 永久信任curl

2.2.2 curl携带客户端证书

2.2.3 Java客户端实现


0.HTTPS

HTTPS与HTTP的区别就是基于SSL层,而不是直接基于TCP,

HTTPS VS HTTP区别

  1. 建立连接过程,HTTP的话几乎在TCP建连(3次握手)之后就可以传输应用数据了,但是HTTPS在TCP建连后,需要在SSL层经历SSL握手才能建立好SSL连接!
  2. 传输过程中,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)即可实现。

由此可见通信两端的证书都用到了

猜你喜欢

转载自blog.csdn.net/sarafina527/article/details/89451676