zoukankan      html  css  js  c++  java
  • https自签名证书双向认证

    前言

    本篇文章主要介绍的是OpenSSL生成自签名证书,实现https双向认证。

    一、在linux中使用OpenSSL生成CA证书、客户端证书、服务端证书

    查看 OpenSSL版本号 openssl version -a
    如果不存在,需要安装OpenSSL
    下载地址:www.openssl.org/source/openssl-1.0.2p.tar.gz

    tar -zxv openssl-1.0.2p.tar.gz
    cd openssl-1.0.2p/
    ./config
    make && make install
    ./config shared 
    make clean
    make  && make install
    
    1.在nginx目录下创建ssl目录,进入ssl,运行下面这个脚本,最后输入客户端密码
    #!/bin/bash
    # function: 创建 nginx https ,双向认证证书
    #
    # BEGIN
    #
    # 网站域名
    # 在签发服务(客户)端证书的时候
    # 这个域名必须跟 subj 中的 CN 对应
    # 否则浏览器会报不安全的链接
    # 查找所有javatest.hqxapp.com替换成域名运行即可
    domain="javatest.hqxapp.com"
    #
    # ---------- CA ----------
    #
    # 准备CA密钥
    echo "创建CA密钥.."
    openssl genrsa -out $domain.CA.key 2048
    # 生成CA证书请求
    # 证书请求都是根据私钥来生成的
    echo "生成CA证书请求.."
    openssl req -new -key $domain.CA.key -out $domain.CA.csr -days 365 -subj /C=CN/ST=GuangDong/L=GuangZhou/O=javatest.hqxapp.com/OU=javatest.hqxapp.com/CN=opcenter/emailAddress=javatest.hqxapp.com -utf8
     
    # 签名CA证书请求
    # 使用自己的私钥来给这个CA证书请求签名
    # 经过多次测试得知: 这个时间如何设置的太长,如 3650(10年) 
    # chrome浏览器会报, 该网站使用的安全设置已过期
    # 所以https不会显示是绿色, 而是带一个黄色三角形的图标
    # 奇怪的是: 如果设成1年,也就是365天,不会提示该网站使用的安全设置已过期
    # 而且也是绿色的标识
    # 但如果是2年, 也是绿色的标识,但是会提示该网站使用的安全设置已过期
    # 这个应该chrome浏览器的问题
    echo "创建CA证书.."
    openssl x509 -req -in $domain.CA.csr -signkey $domain.CA.key -out $domain.CA.crt -days 365
     
    # CA证书转换为DER格式,
    # DER格式似乎更加通用
    openssl x509 -in $domain.CA.crt -out $domain.CA.der -outform DER
    # 现在, 终于拿到了自己做 CA 需要的几个文件了, 
    # 密钥: $domain.CA.key
    # 证书: $domain.CA.crt
    # 系统使用的: $domain.CA.der
    # 接下来, 要创建一个网站, 就需要让 CA 给他签名一个证书了
     
    #
     # --------- SERVER ----------
     #
     # 准备网站密钥
     echo "创建网站(服务端)密钥.."
     openssl genrsa -out $domain.server.key 2048
     # 生成网站证书请求
     # CN 一定要是网站的域名, 否则会通不过安全验证
     echo "生成网站(服务端)证书请求.."
     openssl req -new -key $domain.server.key -out $domain.server.csr -days 365 -subj /C=CN/ST=GuangDong/L=GuangZhou/O=javatest.hqxapp.com/OU=javatest.hqxapp.com/CN=$domain/emailAddress=javatest.hqxapp.com -utf8
      
      # CA签名网站证书请求
      # 不是拿到 CA 的证书了就可以说自己是 CA 的, 最重要的是, 签名需要有 CA 密钥
      # 如果客户端(个人浏览器)信任 CA 的证书的话, 那么他也就会信任由 CA 签名的网站证书
      # 因此让浏览器信任 CA 的证书之后, 客户端就自然信任服务端了, 只要做单向认证的话, 到这一步证书这一类材料就已经准备好了
      # 但是双向认证就还要给客户端(个人的浏览器)准备一份证书
      # 让服务端可以知道客户端也是合法的。
      # 假如让服务端也信任 CA 的证书
      # 那 CA 签名的客户端证书也就能被信任了。
      echo "通过CA证书签名, 创建网站(服务端)证书.."
      openssl x509 -req -in $domain.server.csr -out $domain.server.crt -CA $domain.CA.crt -CAkey $domain.CA.key -CAcreateserial -days 365
       
    #
    # --------- CLIENT ----------
    #
    # 准备客户端私钥
    echo "创建浏览器(客户端)密钥.."
    openssl genrsa -out $domain.client.key 2048
    # 生成客户端证书请求
    echo "生成浏览器(客户端)证书请求.."
    openssl req -new -key $domain.client.key -out $domain.client.csr -days 3650 -subj /C=CN/ST=GuangDong/L=GuangZhou/O=javatest.hqxapp.com/OU=javatest.hqxapp.com/CN=$domain/emailAddress=javatest.hqxapp.com -utf8
    # CA签名客户端证书请求
    echo "通过CA证书签名, 创建浏览器(客户端)证书.."
    openssl x509 -req -in $domain.client.csr -out $domain.client.crt -CA $domain.CA.crt -CAkey $domain.CA.key -CAcreateserial -days 365 
    # 客户端证书转换为DER格式
    openssl x509 -in $domain.client.crt -out $domain.client.der -outform DER
    # 客户端证书转换为 PKCS, 即12格式
    # 全称应该叫做 Personal Information Exchange
    # 通常以 p12 作为后缀
    echo "转换客户端证书为p12格式.."
    openssl pkcs12 -export -in $domain.client.crt -inkey $domain.client.key -out $domain.client.p12
    

    运行该脚本前需要赋予权限:chmod 777 ssl.sh

    生成证书:

    2.在nginx中配置

    vim nginx/conf/nginx.conf

    server {
            listen       443;
            server_name  localhost;
            ssi on;
            ssi_silent_errors on;
            ssi_types text/shtml;
     
             ssl on;
             ssl_certificate ../ssl/javatest.hqxapp.com.server.crt;
             ssl_certificate_key  ../ssl/javatest.hqxapp.com.server.key;
             ssl_client_certificate ../ssl/javatest.hqxapp.com.CA.crt;
             ssl_session_timeout  5m;
             ssl_verify_client on;
             ssl_protocols  SSLv2 SSLv3 TLSv1;
             ssl_ciphers  RC4:HIGH:!aNULL:!MD5;
             ssl_prefer_server_ciphers   on;
            
         location / {
                index index.html index.htm;
                proxy_pass http://127.0.0.1:8080;
            }
        }
    
    
    3.报错nginx: [emerg] unknown directive “ssl” in /usr/local/nginx/conf/nginx.conf

    原因:因为配置这个SSL证书需要引用到nginx的中SSL这模块,然而一开始编译的Nginx的时候并没有把SSL模块一起编译进去,所以导致这个错误的出现。
    解决方案:
    1 ./configure --with-http_ssl_module //重新添加这个ssl模块
    2 执行make命令,但是不要执行make install,因为make是用来编译的,而make install是安装,然整个nginx会重新覆盖的。
    3 在我们执行完做命令后,我们可以查看到在nginx解压目录下,objs文件夹中多了一个nginx的文件,这个就是新版本的程序了。首先我们把之前的nginx先备份一下,然后把新的程序复制过去覆盖之前的即可。(先停止nginx程序)
    cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
    cp objs/nginx /usr/local/nginx/sbin/nginx
    4 最后来到Nginx安装目录下,来查看是否有安装ssl模块成功。执行./sbin/nginx -V即可看到。
    最后,进入nginx/sbin,./nginx –s reload ,重启nginx。

    二、 使用客户端证书访问https(证书有改动(生成新的证书),需要重启nginx)

    1. 浏览器

    双击javatest.hqxapp.com.client.p12,安装该证书,一直下一步直至完成,期间要输入的密码,是运行生成证书的脚本时,最后一步输入的密码。然后重启浏览器即可。
    不安装该证书访问,会报以下错误

    2. postman

    关闭验证

    添加秘钥

    请求接口

    3. java代码
    1.获取证书(server.crt转换为server.bks)

    keytool -importcert -v -trustcacerts -alias ttt -file 位置1 -keystore 位置2 -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath 位置3 -storepass 位置4

    位置1:server.crt证书的绝对路径
    位置2:要生成的server.bks文件的绝对路径
    位置3:转换jar包的绝对路径
    位置4:要设置的证书密码

    举例:keytool -importcert -v -trustcacerts -alias ttt -file C:Users20180030Desktopssljavatest.hqxapp.com.server.crt -keystore C:Users20180030Desktopssljavatest.hqxapp.com.server.bks -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath D:SSLcprov-jdk15on-163.jar -storepass 密码

    2.server.bks证书转换为server.jks证书

    server.jks证书才是java最终要用到的服务端证书。
    将两个证书文件放到项目根目录下创建的key文件夹下。打开portecle.jar,启动该工具。

    打开server.bks文件


    输入转换bks命令时设置的密码

    转换为jks格式

    另存为,获得jks证书

    调用java代码

    package com.hqx.util;
    
    import com.alibaba.fastjson.JSONObject;
    import org.junit.Test;
    
    import javax.net.ssl.*;
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.security.*;
    import java.security.cert.CertificateException;
    import java.util.List;
    import java.util.Map;
    
    
    public class HttpsClient {
        public static String KEY_STORE_FILE="key/javatest.hqxapp.com.client.p12";
        public static String KEY_STORE_PASS="密码";
        /**
         * server.crt转换bks,再转为jks格式
         */
        public static String TRUST_STORE_FILE="key/javatest.hqxapp.com.server.jks";
        public static String TRUST_STORE_PASS="密码";
    
    
        private static SSLContext sslContext;
        /**
         * 向指定URL发送GET方法的请求 
         *
         * @param url
         *            发送请求的URL 
         * @param param
         *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 
         * @return URL 所代表远程资源的响应结果 
         *
         */
        public static String sendGet(String url, String param) {
            String result = "";
            BufferedReader in = null;
            try {
                String urlNameString = url + "?" + param;
                URL realUrl = new URL(urlNameString);
                // 打开和URL之间的连接  
                HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
                // 打开和URL之间的连接  
                if(connection instanceof HttpsURLConnection){
                    ((HttpsURLConnection)connection)
                            .setSSLSocketFactory(getSSLContext().getSocketFactory());
                }
    
                // 设置通用的请求属性  
                connection.setRequestProperty("accept", "*/*");
                connection.setRequestProperty("connection", "Keep-Alive");
                connection.setRequestProperty("user-agent",
                        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
                // 建立实际的连接  
                connection.connect();
                // 获取所有响应头字段  
                Map<String, List<String>> map = connection.getHeaderFields();
                // 遍历所有的响应头字段  
                for (String key : map.keySet()) {
                    //System.out.println(key + "--->" + map.get(key));  
                }
                // 定义 BufferedReader输入流来读取URL的响应  
    
                if(connection.getResponseCode()==200){
                    in = new BufferedReader(new InputStreamReader(
                            connection.getInputStream(),"utf8"));
                }else{
                    in = new BufferedReader(new InputStreamReader(
                            connection.getErrorStream(),"utf8"));
                }
                String line;
                while ((line = in.readLine()) != null) {
                    result += line;
                }
    
            } catch (Exception e) {
                System.out.println("发送GET请求出现异常!" + e);
                e.printStackTrace();
            }
            // 使用finally块来关闭输入流  
            finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            return result;
        }
    
        /**
         * 向指定 URL 发送POST方法的请求  
         *
         * @param url
         *            发送请求的 URL  
         * @param param
         *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。  
         * @return 所代表远程资源的响应结果
         */
        public static String sendPost(String url, String param) {
            PrintWriter out = null;
            BufferedReader in = null;
            String result = "";
            try {
                URL realUrl = new URL(url);
                // 打开和URL之间的连接  
                HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
                if(conn instanceof HttpsURLConnection){
                    ((HttpsURLConnection)conn)
                            .setSSLSocketFactory(getSSLContext().getSocketFactory());
                }
                // 设置通用的请求属性  
                conn.setRequestProperty("accept", "*/*");
                conn.setRequestProperty("connection", "Keep-Alive");
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("user-agent",
                        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
                // 发送POST请求必须设置如下两行  
                conn.setDoOutput(true);
                conn.setDoInput(true);
                // 获取URLConnection对象对应的输出流  
                out = new PrintWriter(conn.getOutputStream());
                // 发送请求参数  
                out.print(param);
                // flush输出流的缓冲  
                out.flush();
                // 定义BufferedReader输入流来读取URL的响应  
                if(conn.getResponseCode()==200){
                    in = new BufferedReader(
                            new InputStreamReader(conn.getInputStream(),"utf8"));
                }else{
                    in = new BufferedReader(
                            new InputStreamReader(conn.getErrorStream(),"utf8"));
                }
                String line="";
                while ((line = in.readLine()) != null) {
                    result += line;
                }
            } catch (Exception e) {
                System.out.println("发送 POST 请求出现异常!"+e);
                e.printStackTrace();
            }
            //使用finally块来关闭输出流、输入流  
            finally{
                try{
                    if(out!=null){
                        out.close();
                    }
                    if(in!=null){
                        in.close();
                    }
                }catch(IOException ex){
                    ex.printStackTrace();
                }
            }
            return result;
        }
    
        public static SSLContext getSSLContext(){
            long time1=System.currentTimeMillis();
            if(sslContext==null){
                try {
                    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                    kmf.init(getkeyStore(),KEY_STORE_PASS.toCharArray());
                    KeyManager[] keyManagers = kmf.getKeyManagers();
    
                    TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance("SunX509");
                    trustManagerFactory.init(getTrustStore());
                    TrustManager[]  trustManagers= trustManagerFactory.getTrustManagers();
    
                    sslContext = SSLContext.getInstance("TLS");
                    sslContext.init(keyManagers, trustManagers, new SecureRandom());
                    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }
                    });
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (UnrecoverableKeyException e) {
                    e.printStackTrace();
                } catch (KeyStoreException e) {
                    e.printStackTrace();
                } catch (KeyManagementException e) {
                    e.printStackTrace();
                }
            }
            long time2=System.currentTimeMillis();
            System.out.println("SSLContext 初始化时间:"+(time2-time1));
            return sslContext;
        }
    
    
        public static KeyStore getkeyStore(){
            KeyStore keySotre=null;
            try {
                keySotre = KeyStore.getInstance("PKCS12");
                File file = new File(KEY_STORE_FILE);
                System.out.println(file.getAbsolutePath());
                FileInputStream fis = new FileInputStream(new File(KEY_STORE_FILE));
                keySotre.load(fis, KEY_STORE_PASS.toCharArray());
                fis.close();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return keySotre;
        }
        public static KeyStore getTrustStore() throws IOException{
            KeyStore trustKeyStore=null;
            FileInputStream fis=null;
            try {
                trustKeyStore=KeyStore.getInstance("JKS");
                fis = new FileInputStream(new File(TRUST_STORE_FILE));
                trustKeyStore.load(fis, TRUST_STORE_PASS.toCharArray());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                fis.close();
            }
            return trustKeyStore;
        }
    
        @Test
        public static void main(String[] args) throws UnsupportedEncodingException {
            String result=sendGet("https://javatest.hqxapp.com/demo/", "");
            System.out.println(result);
        }
    
        @Test
        public void t() {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("content", "111");
            String result=sendPost("https://javatest.hqxapp.com/demo/", jsonObject.toJSONString());
            System.out.println(result);
        }
    }  
    

    上面用到的jar包、脚本、代码可在资源模块中下载。

    CSDN:https://blog.csdn.net/qq_27682773
    简书:https://www.jianshu.com/u/e99381e6886e
    博客园:https://www.cnblogs.com/lixianguo
    个人博客:https://www.lxgblog.com

  • 相关阅读:
    小喵的在线共享编辑器
    简易漫画网站搭建-漫画喵Server版
    爬虫-漫画喵的100行逆袭
    应用OpenMP的一个简单的设计模式
    基于Caffe的Large Margin Softmax Loss的实现(中)
    基于Caffe的Large Margin Softmax Loss的实现(上)
    Oracle GoldenGate OGG管理员手册
    Spark快速大数据分析之RDD基础
    Apache Spark大数据分析入门(一)
    一文教你看懂大数据的技术生态圈:Hadoop,hive,spark
  • 原文地址:https://www.cnblogs.com/lixianguo/p/12522557.html
Copyright © 2011-2022 走看看