关于https的双向认证详解tomcat+java实现交叉验证

  • Post author:
  • Post category:java



关于https的双向认证详解

原doc文档导出pdf


https://github.com/enderwsp/share4u/raw/master/%E5%85%B3%E4%BA%8Ehttps%E7%9A%84%E5%8F%8C%E5%90%91%E8%AE%A4%E8%AF%81%E8%AF%A6%E8%A7%A3tomcat%2Bjava%E5%AE%9E%E7%8E%B0%E4%BA%A4%E5%8F%89%E9%AA%8C%E8%AF%81.pdf


基本描述

Https是在基本的http通讯协议上增加的tls安全协议,tls安全协议需要ssl安全证书作为加解密要素,双向认证是指在客户端确认服务的是否可信任的单向认证基础上增加服务端的对客户端的认证。


主要内容


https通信协议


是以安全为目标的


HTTP


通道,简单讲是


HTTP


的安全版。即


HTTP


下加入


SSL


层,


HTTPS


的安全基础是


SSL


,因此加密的详细内容就需要


SSL


参见



百度https百科


tls安全协议

SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密


参见



百度SSL


ssl安全证书


SSL


证书就是遵守


SSL


协议,由受信任的数字证书颁发机构


CA


,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能

参见

百度ssl证书


证书链



证书链



的描述,证书链的可信传递机制,以及根证书的来源和查看方式


证书格式

常见证书格式和转换


https://blog.csdn.net/justinjing0612/article/details/7770301


tls认证过程


SSL


认证是指



客户端



到服务器端的认证。主要用来提供对用户和服务器的认证;对传送的数据进行加密和隐藏;确保数据在传送中不被改变,即数据的完整性,现已成为该领域中全球化的标准,即所谓的单向认证

参见

百度ssl认证


其他内容

HTTPS实战之单向验证和双向验证


https://www.jianshu.com/p/119c4dbb7225

浅谈HTTPS(SSL/TLS)原理


https://www.jianshu.com/p/41f7ae43e37b

HTTPS通信中的身份认证机制


https://blog.csdn.net/bravegogo/article/details/60766773

SSL双向认证以及证书的制作和使用-https+客户端身份验证


https://blog.csdn.net/soarheaven/article/details/78784848

X – Certificate and Key management证书管理工具


https://hohnstaedt.de/xca/index.php

ssl证书相当于安全的钥匙,如果私钥外泄被恶意盗用,会存在安全问题


双向认证的常用场景


银行网银U盾使用

专业版登录支持U盾认证多重认证

资金动账类交易U盾再次认证确认客户端身份


金融支付工具安全数字证书

支付宝数字证书app下载安装可以提高app支付安全,支付额度

常用pc安装数字证书免除重复的其他安全校验


其他对通信安全有较高要求的都可以应用此https双向认证

主要成本在于向具有CA资质的机构申请有效的证书,非CA机构的自制根证书及证书链管理需要自行负责


https应用的使用示例


工具版本说明

本地操作系统版本:win10

本地tomcat版本9

本地java版本1.8

证书管理工具xca 2.1.2

Keytool界面工具1.6


http://enkj.jb51.net:81/201703/tools/keytool_jb51.rar


前提概要

客户端和服务端角色根据通信请求来确定,客户端和服务端需要保持支持一致的TLS安全协议版本


单向认证说明

单向认证首先需要申请制作证书链

对于客户端请求服务端,比较常见的客户端是指浏览网页使用的浏览器(chrome,IE,firefox等)以及移动客户端(ios或android app),服务端一般指客户端请求目标服务的服务提供者


双向认证Java的https客户端请求


Keystool工具使用

请参考网络使用说明

依照java的keytool命令界面化工具,生成java专有的jks格式容器文件


依赖包

<dependency>

            <groupId>org.apache.commons</groupId>

            <artifactId>commons-io</artifactId>

            <version>1.3.2</version>

        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->

        <dependency>

            <groupId>org.apache.httpcomponents</groupId>

            <artifactId>httpclient</artifactId>

            <version>4.5.9</version>

        </dependency>


Java client demo

package com.web.ssl.twoway.ssltwoway;



import com.web.ssl.twoway.ssltwoway.Constants;

import org.apache.commons.io.IOUtils;

import org.apache.http.HttpEntity;

import org.apache.http.client.methods.CloseableHttpResponse;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;

import org.apache.http.conn.ssl.TrustSelfSignedStrategy;

import org.apache.http.conn.ssl.TrustStrategy;

import org.apache.http.impl.client.CloseableHttpClient;

import org.apache.http.impl.client.HttpClients;

import org.apache.http.ssl.SSLContexts;

import org.apache.http.util.EntityUtils;

import com.web.ssl.twoway.ssltwoway.Constants;



import javax.net.ssl.SSLContext;

import java.io.File;

import java.io.FileInputStream;

import java.security.KeyStore;

import java.security.cert.CertificateException;

import java.security.cert.X509Certificate;



public class SSLhttps {





    public static void main(String[] args) throws Exception {

        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        keyStore.load(new FileInputStream(new File(Constants.clientPrivateFile)), Constants.clientPrivatePassword.toCharArray());

        SSLContext sslcontext = SSLContexts.custom()

                //忽略掉对服务器端证书的校验

                .loadTrustMaterial(new TrustStrategy() {

                    public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                        return true;

                    }

                })



                //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了)

                .loadTrustMaterial(new File(Constants.trustAcFile), Constants.trustAcFilePassword.toCharArray(),

                        new TrustSelfSignedStrategy())

                .loadKeyMaterial(keyStore, Constants.clientPrivatePassword.toCharArray())

                .build();

        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(

                sslcontext,

                new String[]{"TLSv1"},

                null,

                SSLConnectionSocketFactory.getDefaultHostnameVerifier());

        CloseableHttpClient httpclient = HttpClients.custom()

                .setSSLSocketFactory(sslConnectionSocketFactory)//取消改行注释不导入运行环境的客户端证书 AAA

                .build();



        try {



            HttpGet httpget = new HttpGet("https://localhost:8443/");

//            若注释 AAA 行的内容,请求需要双向认证的服务会出现以下异常

//unable to find valid certification path to requested target

            System.out.println("Executing request " + httpget.getRequestLine());



            CloseableHttpResponse response = httpclient.execute(httpget);

            try {

                HttpEntity entity = response.getEntity();

                System.out.println(response.getStatusLine());

                System.out.println(IOUtils.toString(entity.getContent()));

                EntityUtils.consume(entity);

            } finally {

                response.close();

            }

        } finally {

            httpclient.close();

        }

    }

}


Java httpsServer demo

package com.web.ssl.twoway.ssltwoway;



import java.io.BufferedReader;

import java.io.FileInputStream;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.io.PrintWriter;

import java.security.KeyStore;

import java.security.cert.Certificate;



import javax.net.ssl.KeyManager;

import javax.net.ssl.KeyManagerFactory;

import javax.net.ssl.SSLContext;

import javax.net.ssl.SSLServerSocket;

import javax.net.ssl.SSLServerSocketFactory;

import javax.net.ssl.SSLSession;

import javax.net.ssl.SSLSocket;

import javax.net.ssl.TrustManager;

import javax.net.ssl.TrustManagerFactory;

import javax.security.cert.X509Certificate;



public class HTTPSServer {

    private boolean isServerDone = false;



    public static void main(String[] args) {

        HTTPSServer server = new HTTPSServer();

        server.run();

    }



    // Create the and initialize the SSLContext

    private SSLContext createSSLContext() {

        try {

            KeyStore keyStore = KeyStore.getInstance("PKCS12");

            keyStore.load(new FileInputStream(Constants.serverPrivateFile), Constants.serverPrivatePassword.toCharArray());



            // Create key manager

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");

            keyManagerFactory.init(keyStore, Constants.serverPrivatePassword.toCharArray());

            KeyManager[] km = keyManagerFactory.getKeyManagers();

            KeyStore keyStoreTrust = KeyStore.getInstance("JKS");

            keyStoreTrust.load(new FileInputStream(Constants.trustAcFile), Constants.trustAcFilePassword.toCharArray());

            // Create trust manager

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");

            trustManagerFactory.init(keyStoreTrust);

            TrustManager[] tm = trustManagerFactory.getTrustManagers();



            // Initialize SSLContext

            SSLContext sslContext = SSLContext.getInstance("TLS");

            sslContext.init(km, tm, null);



            return sslContext;

        } catch (Exception ex) {

            ex.printStackTrace();

        }



        return null;

    }



    // Start to run the server

    public void run() {

        SSLContext sslContext = this.createSSLContext();



        try {

            // Create server socket factory

            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();



            // Create server socket

            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(Constants.DEFAULT_PORT);



            System.out.println("SSL server started");

            while (!isServerDone) {

                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();



                // Start the server thread

                new ServerThread(sslSocket).start();

            }

        } catch (Exception ex) {

            ex.printStackTrace();

        }

    }



    // Thread handling the socket from client

    static class ServerThread extends Thread {

        private SSLSocket sslSocket = null;



        ServerThread(SSLSocket sslSocketInit) {

            sslSocket = sslSocketInit;

            sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());

            sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());

            sslSocket.setNeedClientAuth(true);

            sslSocket.setWantClientAuth(true);

            sslSocket.setEnableSessionCreation(true);

        }



        public void run() {



            try {

                // Start handshake

                sslSocket.startHandshake();



                // Get session after the connection is established

                SSLSession sslSession = sslSocket.getSession();



                System.out.println("SSLSession :");

                System.out.println("\tProtocol : " + sslSession.getProtocol());

                System.out.println("\tCipher suite : " + sslSession.getCipherSuite());



                // Start handling application content

                InputStream inputStream = sslSocket.getInputStream();

                OutputStream outputStream = sslSocket.getOutputStream();



                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));



                String line = null;

                while ((line = bufferedReader.readLine()) != null) {

                    System.out.println("Inut : " + line);



                    if (line.trim().isEmpty()) {

                        break;

                    }

                }

                boolean clientCertValid = false;

                try {

                    Certificate[] c1 = sslSession.getLocalCertificates();

                    for (Certificate c1One : c1) {

                        System.out.println("getLocalCertificates : " + c1One.toString());

                    }

                    X509Certificate[] c2 = sslSession.getPeerCertificateChain();

                    for (X509Certificate c1One : c2) {

                        System.out.println("getPeerCertificateChain : " + c1One.toString());

                    }

                    Certificate[] c3 = sslSession.getPeerCertificates();

                    for (Certificate c1One : c3) {

                        System.out.println("getPeerCertificates : " + c1One.toString());

                    }

                    clientCertValid = true;

                } catch (Exception cex) {



                }

                if (clientCertValid) {

                    // Write data

                    printWriter.print("HTTP/1.1 200 OK\r\nServer: johnserver/1.0.8.18\r\nContent-Length: 3\r\n\r\nok\n");



                    printWriter.flush();

                } else {

                    // Write data

                    printWriter.print("HTTP/1.1 200 FAILED\r\nServer: johnserver/1.0.8.18\r\nContent-Length: 6\r\n\r\nFAILED\n");



                    printWriter.flush();

                }

                sslSocket.close();

            } catch (Exception ex) {

                ex.printStackTrace();

            }

        }

    }

}


证书链制作

--也可以使用其他证书管理工具(keytool、openssl等)


先下载安装xca工具,地址是


http://xca.hohnstaedt.de/



先用xca创建一本ca证书,


本次说明使用的版本为2.1.2


运行C:\Aprograms\Agreen\xca-portable-2.1.2\xca-portable-2.1.2\xca.exe


后打开的界面


依次File– New DataBase,


选择xdb文件保存路径,输入文件名,点击保存,


再输入密码(演示密码123456)


点击OK确定后,回到主界面


切换到Certificates页面,点击New Certificate


创建根证书,


在source栏,签名算法选择SHA 512,证书模版选择默认CA,再点击Apply all


修改任何内容记得保存


在extensions栏,type选择


Certificate Authority


可选


—time range


有效期设置


10


年后点击


apply


Subject


栏,填好各个字段,都可以随便填


点击Generate a new key生产私钥

不修改内容,点击Create

然后点击新建证书的OK’


CA


证书做好了,有效期默认10年


根证书导出成只包含公钥的证书格式,这本根证书就是放在网站上供用户下载安装,或主动安装到客户机器中的

选择创建好的根证书,点击导出export

Filename可以选择导出文件路径

点击OK


制作服务器证书、客户端证书和制作CA证书差不多,只有两个地方不一样:


1


选择已经制作好的根CA,然后点击New Certificate1

2


签名时,选择使用根证书,这里是seeme根证书进行签名颁发,然后证书模版选择服务器HTTPS SERver(制作客户端证书就选择HTTPS_client),其他都和制作根证书一样,然后点击Apply all(这个一定不能忘),然后再切到Subject、Extension页面填写相应的东西就OK了


Server


证书

Extensions不修改

Subject填入信息后点击generate生成证书

点击create

点击新建证书的OK

Server证书创建成功后的展示


再将服务器证书导出来,选择p12格式

输入server p12导出的密码

(演示的密码112233)

新增https client证书

点击右下角的generate

点击create

点击OK

演示的密码111222

所有的证书文件


演示的证书passwords密码


Xdb证书容器123456


服务器私钥123123


客户端私钥111222




个人客户端证书导入注意

修改本地客户端证书(删除或者重新导入)

千万记得点击清除SSL状态


tomcat配置准备

作为双向认证支持的web容器,用作java实现的客户端和服务端交叉验证双向认证有效性


Tomcat9下载


https://tomcat.apache.org/download-90.cgi

导出的服务器p12证书放在tomcat的conf/sslcerts目录下,(当然也可以使用绝对路径)


修改默认配置,打开https,设置单向认证

Conf/server.xml

Service节点新增

<Connector port=”8443″ protocol=”org.apache.coyote.http11.Http11NioProtocol”

maxThreads=”150″ SSLEnabled=”true” secure=”true” slProtocol=”TLS”>

<SSLHostConfig>

<Certificate certificateKeystoreFile=”conf/sslcerts/server.p12″ certificateKeystoreType=”PKCS12″ certificateKeystorePassword=”123123″ />

</SSLHostConfig>

</Connector>

Conf/web.xml

welcome-file-list节点后新增

<login-config>

<!– Authorization setting for SSL –>

<auth-method>CLIENT-CERT</auth-method>

<realm-name>Client Cert Users-only Area</realm-name>

</login-config>

<security-constraint>

<!– Authorization setting for SSL –>

<web-resource-collection >

<web-resource-name >SSL</web-resource-name>

<url-pattern>/*</url-pattern>

</web-resource-collection>

<user-data-constraint>

<transport-guarantee>CONFIDENTIAL</transport-guarantee>

</user-data-constraint>

</security-constraint>


安装CA根证书

双击root.crt

点击安装证书

选择本地计算机

点下一步

选择 将所有的证书都放入下列存储

点击浏览选择受信任的根证书机构

点击确定

点击下一步直到完成


验证单向认证tomcat


启动tomcat


打开地址https://localhost:8443/

点击证书查看按钮(IE 为一个锁的形状)

点击查看证书

点击证书路径

显示




该证书没有问题




就是正常的


配置双向认证tomcat


配置需求说明

本次使用的tomcat9


服务端

在原有服务的私钥证书配置好的基础上,新增证书容器(包含客户端证书的根证书,能认证客户端公钥有效性的)


客户端

在原有的(安装服务端证书的根证书后)认证服务端证书的同时,提供客户端公钥证书给服务端ssl请求


Tomcat配置修改

在新增的connector中


<Connector protocol=”org.apache.coyote.http11.Http11NioProtocol”


sslImplementationName=”org.apache.tomcat.util.net.jsse.JSSEImplementation”


port=”8443″ maxThreads=”200″


scheme=”https” secure=”true” SSLEnabled=”true”


keystoreFile=”conf/sslcerts/localhost.p12″ keystorePass=”123123″


truststoreFile=”conf/sslcerts/trustme.jks” truststorePass=”1q2w3e”


clientAuth=”true” sslProtocol=”TLS” />

重启tomcat 后


双向验证服务端检查

打开IE(显示不明确),chrome(本地暂无客户端私钥,客户端证书暂时不提供)

提示

ERR_BAD_SSL_CLIENT_AUTH_CERT


表示当前浏览器访问


ssl


核验客户端证书失败


双向认证客户端检查


导入客户端证书到本机证书内容

IE –工具—

打开internet选项

点击内容栏

点击证书

点击导入证书

点击下一步

点击浏览选择客户端私钥文件

点击下一步

输入客户端私钥密码

勾选启用强私钥保护

点击下一步直到导入成功,

个人类的证书列表里面会多出刚导入的客户端证书


刷新浏览器


选择证书

选择后续所有的允许

最好能成功打开页面


注意双向认证权限读取申请

浏览器针对同一地址的双向认证才会弹出证书选择框

后续出现的安全要点允许

有需要重复操作的请按照



个人客户端证书导入注意


本文的java demo 工程地址


https://github.com/enderwsp/sslDemo

工程内包括已经生成的服务器证书 客户端证书 根证书

本来是有一个ngnix的web服务版本做交叉验证的,但没有能和xca证书管理工具很好的搭配使用(不能导出key文件证书,需要openssl命令)



版权声明:本文为Ender__原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。