tomcat8配置https,实现单双向SSL


花了两三天终于搞完,中间有个环节卡了一天,不然一天就能搞定了。具体https、ssl是啥,我就不说了哈,不懂的先去百度。

废话不多说,下面开始正题。

使用tomcat8+jdk8。

一、单向

1、生成服务器证书库,及秘钥,keytool是jdk自带的,也可以使用openssl。

keytool-genkey -v -alias mykeystore -keyalg RSA -keystore server.keystore  -validity 36500

tomcat8配置https,实现单双向SSL_第1张图片

2、查看证书库keytool -list -v -keystore server.keystore

tomcat8配置https,实现单双向SSL_第2张图片

3、导出公钥证书,用于浏览器安装

     keytool -export -alias mykeystore -keystore server.keystore -rfc -file client.cer,名称什么的怕弄混,自己看着改吧。

 

生成之后点击安装,直接按照默认的就行了,正常都没问题,不放心可以再多安装几个目录。这个证书也可以不安装,不安装你就需要在浏览器上面点击信任继续访问什么的,就跟之前12306的那个网站的呢。

4、配置tomcat

disableUploadTimeout="true"enableLookups="false"maxThreads="25"

  port="8443"keystoreFile="/etc/starry/starryThc/server.keystore"  keystorePass="123456"

   protocol="org.apache.coyote.http11.Http11NioProtocol"  scheme="https"

secure="true"sslProtocol="TLS" />

访问:

 tomcat8配置https,实现单双向SSL_第3张图片

至此,单向ssl实际上已经完成了。https上面有个红色的叉叉,说明浏览器不信任这个服务器的证书。
这个叉叉的原因是证书的CN,没有与主机名(公网时使用域名)保持一致,因为我使用的是本机装的虚拟机,没有配置域名,我服务器的这个CN配置的是YU,所以我在本地hosts中配置了
192.168.10.244 YU,大小写貌似没区别。

再访问,变成绿色的了

 

通过上面两张截图可以看出,在使用hosts配置局域网的这个主机名之后,浏览器认为他是安全的了。

好了,单向的SSL,浏览器端已经没有问题了,现在来写java后台实现访问。

5、java后台访问 

     不带证书的post


sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

提示没有证书

跟我后台访问百度是一个样子。没有证书。

这个错误的原因是,jdk的证书信任库中没有该证书,为什么后台访问淘宝的没问题,就是因为淘宝使用的是CA的证书,而百度使用的是verisign家签名的证书,这个证书在浏览器中的是有的,但是在我们的jdk中是没有的,所以在浏览器没有问题,后台进行访问有问题。

下图为百度在浏览器中的证书。

tomcat8配置https,实现单双向SSL_第4张图片

解决这个问题,有两个办法:1代码中带上公钥证书,2将公钥证书导入到jdk的信任库中。

1、编写代码,代码中带入服务器公钥证书

  KeyStore trustKeyStore=KeyStore.getInstance("JKS");
  trustKeyStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray());
  trustManagerFactory.init(trustKeyStore);
  TrustManager[]  trustManagers= trustManagerFactory.getTrustManagers();
            
获取公钥证书。


public String doPostsWithOwnCer(String url,String params,String type,String charset) {
        PrintWriter out = null;
        InputStream in = null;
        String result = null;
        try
        {
            String trustStorePath = "C:\\Users\\Administrator\\Desktop\\学习记录\\keytool\\2018-07-09双向重新配置\\truststore.keystore";
            String trustStorePassword = "123456";
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance("SunX509");
            KeyStore trustKeyStore=KeyStore.getInstance("JKS");
            trustKeyStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray());
            trustManagerFactory.init(trustKeyStore);
            TrustManager[]  trustManagers= trustManagerFactory.getTrustManagers();
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, trustManagers, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            URL uri = new URL(url);
            HttpsURLConnection urlcon = (HttpsURLConnection) uri.openConnection();
            urlcon.setDoInput(true);
            urlcon.setDoOutput(true);
            urlcon.setSSLSocketFactory(ssf);
            urlcon.setRequestMethod(type.toUpperCase());
            urlcon.connect();
            out = new PrintWriter(urlcon.getOutputStream());
//            out.print(params);
            out.write(params);//相比print,write更直观一点
            out.flush();//数据送出
            in = urlcon.getInputStream();
            BufferedReader buffer = new BufferedReader(new InputStreamReader(
                    in, charset));
            StringBuffer bs = new StringBuffer();
            String line = null;
            while ((line = buffer.readLine()) != null) {
                bs.append(line);
            }
            result = bs.toString();
        }
        catch (Exception e)
        {
            logger.error(e.getMessage(),e);
            result = "ERROR:"+e.getMessage();
        }
        logger.info(result);
        return result;
    }

2、将公钥证书导入到jdk的信任库中

keytool-import -alias mykeystore -keystore cacerts -files client.cer

cacerts在jdk的\jre\lib\security目录下。

可以通过别名进行查询相关公钥

tomcat8配置https,实现单双向SSL_第5张图片

上面两种方式都可以解决证书的问题。

接下来可能会提示java.security.cert.CertificateException: Nosubject alternative names present

这个错误就是跟上面那个浏览器红色叉叉的原因一样,主机名跟CN不一致,我们可以通过两种办法避免这个错误。


tomcat8配置https,实现单双向SSL_第6张图片

上面两张图可以看到,我使用YU这个主机名访问是没问题的,但是使用IP地址就不行,就是因为我的证书中的CN配置的是YU,

如果写成ip地址的话也是可以的。

1、添加信任,这种方式是不验证这个CN了,绕过去了,直接返回true。

urlcon.setHostnameVerifier(new HostnameVerifier(){
    @Override
    public boolean verify(String s,SSLSession sslSession) {
        return true;
    }
});

2、就是修改自己的CN了。

到这个地方SSL单向已经完全弄完。

二、SSL双向

1、创建客户端证书,这里一定设定keyalg 为RSA,这个就是困扰我一天的地方。

keytool -genkeypair-v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12  -validity 36500

tomcat8配置https,实现单双向SSL_第7张图片

2、导出公钥证书

keytool -export-alias client -keyalg RSA -keystore client.p12 -file servertrustclient.cer


3、将公钥证书导入信任库中,这个信任库是给tomcat用的。配置在truststoreFile。

keytool -import -v-file servertrustclient.cer -keystore servertrustclient.keystore

tomcat8配置https,实现单双向SSL_第8张图片

4、tomcat配置

SSLEnabled="true" maxThreads="150" scheme="https" secure="true" acceptCount="100"
  clientAuth="true" sslProtocol="TLS" truststoreFile="/etc/starry/starryThc/servertrustclient.keystore"
truststorePass="1112111"
keystoreFile="/etc/starry/starryThc/server.keystore"
keystorePass="123456"/>

5、重启

打开浏览器,我们发现

tomcat8配置https,实现单双向SSL_第9张图片

报错已经变了,这是因为浏览器中没有我们的客户端私钥证书

我们安装刚开始生成的.p12证书。再访问,就可以了。

这个客户端证书我刚开始的时候没有指定加密算法使用什么,keytool的keyalg默认使用了DSA,导致我在chrome、Firefox都一直不能实现双向访问

虽然选择了证书,之后就提示网页无法访问什么的。我前后尝试了好久也没有觉得证书有问题,之所以没有觉得证书有问题,是因为我在ie11中能正常访问,

使用java后台也能正常访问。所以一直认为是浏览器没有将证书带过去。

后来无意中发现前面创建证书是加了RSA的,尝试了一下,发现可以了。是不是跟tomcat也有关系,我没有去尝试,实在是没精力了。

6、双向java后台代码

跟上面的代码基本一样,就是添加了一个客户端证书进去,初始话的时候带上

sslContext.init(keyManagers,trustManagers, new java.security.SecureRandom());

前面是

sslContext.init(null,trustManagers, new java.security.SecureRandom());的写法。


public String doPostsWithOwnCerSSL2(Stringurl,String params,String type,String charset) {
        PrintWriter out = null;
        InputStream in = null;
        String result = null;
        try
        {
            String trustStorePath = "C:\\Users\\Administrator\\Desktop\\学习记录\\keytool\\2018-07-09双向重新配置\\truststore.keystore";
            String trustStorePassword = "123456";
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            String privatecert = "C:\\Users\\Administrator\\Desktop\\学习记录\\证书安全问题\\keytool\\2018-07-09双向重新配置\\client.p12";
            String privatecertPsd = "111111";

            //创建SSL对象
            SSLContext sslContext =SSLContext.getInstance("SSL", "SunJSSE");

            //重写509TrustManager
            //添加信任库,也就是添加服务器的公钥证书进去
            TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance("SunX509");
            KeyStoretrustKeyStore=KeyStore.getInstance("JKS");
            trustKeyStore.load(new FileInputStream(trustStorePath),trustStorePassword.toCharArray());
            trustManagerFactory.init(trustKeyStore);
            TrustManager[]  trustManagers=trustManagerFactory.getTrustManagers();
            //添加私钥证书,也就是客户端创建的p12证书,公钥已经给到服务器,在tomcat中已经配置
            KeyManagerFactory kmf =KeyManagerFactory.getInstance("SunX509","SunJSSE");
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(new FileInputStream(privatecert),privatecertPsd.toCharArray());
           kmf.init(keyStore,privatecertPsd.toCharArray());
            KeyManager[] keyManagers = kmf.getKeyManagers();
            sslContext.init(keyManagers,trustManagers, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf =sslContext.getSocketFactory();
            URL uri = new URL(url);
            HttpsURLConnection urlcon =(HttpsURLConnection) uri.openConnection();
            urlcon.setDoInput(true);
            urlcon.setDoOutput(true);
           urlcon.setSSLSocketFactory(ssf);

            urlcon.setRequestMethod(type.toUpperCase());

            urlcon.setHostnameVerifier(newHostnameVerifier() {
                @Override
                public boolean verify(Strings, SSLSession sslSession) {
                    return true;
                }
            });
            urlcon.connect();
            out = new PrintWriter(urlcon.getOutputStream());
//            out.print(params);
            out.write(params);//相比print,write更直观一点
            out.flush();//数据送出
            in = urlcon.getInputStream();
            BufferedReader buffer = new BufferedReader(newInputStreamReader(
                    in, charset));
            StringBuffer bs = new StringBuffer();
            String line = null;
            while ((line =buffer.readLine()) != null) {
                bs.append(line);
            }
            result = bs.toString();
        }
        catch (Exception e)
        {
            logger.error(e.getMessage(),e);
            result = "ERROR:"+e.getMessage();
        }
        logger.info(result);
        return result;
    }

到此,ssl单双向就完成了,写的比较匆忙,中间写的估计会有点问题,有啥问题,欢迎留言。





你可能感兴趣的:(学习研究)