Java 基於文件雙向認證的 HTTPS 請求,支持 CICD


1. 原理

雙向驗證即: 客戶端與服務端在進行數據傳輸的時候雙方都需要驗證對方的身份。

在建立 Https 連接的工程中,握手的流程比單向認證多了幾步。

  • 單向認證的過程

    客戶端從服務器端下載服務器公鑰證書進行驗證,然后才建立安全通信通道

  • 雙向認證的過程

    客戶端出了需要從服務器端下葬服務器的公鑰證書進行驗證外,還需要把客戶端的公鑰證書上傳到服務器給服務器端進行驗證,等雙方認證都認證通過之后,才會開始進行安全通信通道

1.1 單向認證流程

單向認證流程中,服務器端保存着公鑰證書和私鑰兩個文件,整個握手過程如下:

 

 

 

 

  1. 客戶端發青建立 HTTPS 連接請求,將 SSL 版本協議的信息發送給服務器端

  2. 服務器端將本機的公鑰證書 ( server.crt ) 發送給客戶端

  3. 客戶端讀取公鑰證書 ( server.crt ) , 獲取服務器端的公鑰

  4. 客戶端生成一個隨機數(密鑰R),用剛才得到的服務器公鑰去加密這個隨機數形成密文,發送給服務端;

  5. 服務端用自己的私鑰(server.key)去解密這個密文,得到了密鑰R

  6. 服務端和客戶端在后續通訊過程中就使用這個密鑰R進行通信了。

     

1.2 雙向認證流程

 

 

 

 

  1. 客戶端發起建立HTTPS連接請求,將SSL協議版本的信息發送給服務端;

  2. 服務器端將本機的公鑰證書(server.crt)發送給客戶端;

  3. 客戶端讀取公鑰證書(server.crt),取出了服務端公鑰;

  4. 客戶端將客戶端公鑰證書(client.crt)發送給服務器端;

  5. 服務器端使用根證書(root.crt)解密客戶端公鑰證書,拿到客戶端公鑰;

  6. 客戶端發送自己支持的加密方案給服務器端;

  7. 服務器端根據自己和客戶端的能力,選擇一個雙方都能接受的加密方案,使用客戶端的公鑰加密后發送給客戶端;

  8. 客戶端使用自己的私鑰解密加密方案,生成一個隨機數R,使用服務器公鑰加密后傳給服務器端;

  9. 服務端用自己的私鑰去解密這個密文,得到了密鑰R

  10. 服務端和客戶端在后續通訊過程中就使用這個密鑰R進行通信了。

 

2. cert 、 key 和 pem 文件區別

推薦閱讀:https://blog.csdn.net/justinjing0612/article/details/7770301

我們當前獲取有3個文件,分別是

  • ca-server.cert.pem

    我們請求到的服務器給頒布的公鑰

  • vendor.my.cert.pem

    我們本地發送客戶端的公鑰 ( 需要發送到服務器端)

  • vendor.my.key.pem

    我們本地客戶端的密鑰

我們可以轉換為我們自己想要的格式

所有的 key 密碼推薦使用 changeit 默認值

先將 pem 轉換為jks ,因為沒有密鑰,所以轉換為 JKS 作為 trustStore 使用

keytool -import -noprompt -file ca-server.cert.pem -keystore ca-server.jks -storepass changeit 

 

而且我們仍要攜帶客戶端的 vendor.my.cert.pem 推送給服務器,而 Java 客戶端無法處理 pem文件,需要將其轉換為 .der 文件或者 p12 文件。

可以通過如下指令

openssl pkcs12 -export -clcerts -in vendor.yundun.cert.pem -inkey vendor.yundun.key.pem -out vendor.yundun.p12

也可以將 pkcs12 轉換為 JKS
不推薦
keytool -importkeystore -srckeystore vendor.my.p12 -srcstoretype PKCS12 -deststoretype JKS -destkeystore vendor.my.jks

 

Java 官方也不建議將 PKCS12 轉換為 JKS

正在將密鑰庫 vendor.my.p12 導入到 vendor.my.jks... 輸入目標密鑰庫口令: 輸入源密鑰庫口令: 已成功導入別名 1 的條目。 已完成導入命令: 1 個條目成功導入, 0 個條目失敗或取消

Warning: JKS 密鑰庫使用專用格式。建議使用 "keytool -importkeystore -srckeystore vendor.my.jks -destkeystore vendor.my.jks -deststoretype pkcs12" 遷移到行業標准格式 PKCS12。

 

3 基於 JAVA 實現 HTTPS 雙向綁定請求

3.1 基於內部代碼實現

3.1.1 證書在 Jar 外部

使用 Java 代碼實現 Https 雙向綁定請求

    private final static String CLIENT_PFX_PATH = "/{your_path}/vendor.my.p12";    //客戶端證書路徑
   
    private final static String CLIENT_PFX_PWD = "password";    //客戶端證書密碼

    private final static String SERVER_PFX_PATH = "/{your_path}/ca-server.jks";    //服務器端證書路徑
    
    private final static String SERVER_PFX_PWD = "password";    // 服務器端證書密碼

        public static void sslRequestGet(String url) throws Exception {
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(getSSLFactory()).build();
        try {
            HttpGet httpget = new HttpGet(url);
            CloseableHttpResponse response = httpclient.execute(httpget);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");//返回結果
                EntityUtils.consume(entity);
                System.out.println(jsonStr);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }



        public static SSLConnectionSocketFactory getSSLFactory() throws Exception{
      // 此時需要查看對應的文件格式,如果公鑰密鑰都存在,建議使用 PKCS12 標准格式
      // 如果只存在公鑰,建議使用 JAVA 支持的 JKS 格式 ( PKCS12 必須要求公密鑰)
      // 轉換 command 在 #2
        KeyStore serverKeyStore = KeyStore.getInstance("jks");
        KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
        try (InputStream clientStream = new FileInputStream(CLIENT_PFX_PATH);
             InputStream serverStream = new FileInputStream(SERVER_PFX_PATH)){
            clientKeyStore.load(clientStream, CLIENT_PFX_PWD.toCharArray());
            serverKeyStore.load(serverStream, SERVER_PFX_PWD.toCharArray());
        }
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(clientKeyStore, CLIENT_PFX_PWD.toCharArray());
        KeyManager[] keyManagers = kmf.getKeyManagers();

        TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(serverKeyStore);
        TrustManager[] trustManagers = tmf.getTrustManagers();

        SSLContext sslcontext = SSLContext.getInstance("TLS");
        sslcontext.init(keyManagers, trustManagers, new java.security.SecureRandom());

        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext
                , new String[]{"TLSv1"}    // supportedProtocols ,這里可以按需要設置
                , null    // supportedCipherSuites
                , SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        return sslsf;
    }

基於純內部代碼實現, 其中各個公密鑰的 Path 推薦放置在打包好的 Jar 文件外部

 

3.1.2 證書在 Jar 內部

這樣可以將證書打包在 Jar 內部,更好在 K8S 或者 CICD 中部署繼承。

java 代碼

   private final static String CLIENT_PFX_PATH = "/{your_path}/vendor.my.p12";    //客戶端證書路徑
   
    private final static String CLIENT_PFX_PWD = "password";    //客戶端證書密碼

    private final static String SERVER_PFX_PATH = "/{your_path}/ca-server.jks";    //服務器端證書路徑
    
    private final static String SERVER_PFX_PWD = "password";    // 服務器端證書密碼

        public static void sslRequestGet(String url) throws Exception {
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(getSSLFactory()).build();
        try {
            HttpGet httpget = new HttpGet(url);
            CloseableHttpResponse response = httpclient.execute(httpget);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");//返回結果
                EntityUtils.consume(entity);
                System.out.println(jsonStr);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }


        public static SSLConnectionSocketFactory getSSLFactory() throws Exception{
      // 此時需要查看對應的文件格式,如果公鑰密鑰都存在,建議使用 PKCS12 標准格式
      // 如果只存在公鑰,建議使用 JAVA 支持的 JKS 格式 ( PKCS12 必須要求公密鑰)
      // 轉換 command 在 #2
        KeyStore serverKeyStore = KeyStore.getInstance(SERVER_PFX_TYPE);
        KeyStore clientKeyStore = KeyStore.getInstance(CLIENT_PFX_TYPE);

        try (InputStream clientStream = getClass().getClassLoader().getResourceAsStream(CLIENT_PFX_PATH);
             InputStream serverStream = getClass().getClassLoader().getResourceAsStream(SERVER_PFX_PATH)) {
            clientKeyStore.load(clientStream, CLIENT_PFX_PWD.toCharArray());
            serverKeyStore.load(serverStream, SERVER_PFX_PWD.toCharArray());
        }
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(clientKeyStore, CLIENT_PFX_PWD.toCharArray());
        KeyManager[] keyManagers = kmf.getKeyManagers();

        TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(serverKeyStore);
        TrustManager[] trustManagers = tmf.getTrustManagers();

        SSLContext sslcontext = SSLContext.getInstance("TLS");
        sslcontext.init(keyManagers, trustManagers, new java.security.SecureRandom());
·
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext
                , new String[]{"TLSv1"}    // supportedProtocols ,這里可以按需要設置
                , null    // supportedCipherSuites
                , SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        return sslsf;
    }

需要在 resources/httpsKeys 文件夾下存放相對應的文件

3.2 使用命令行指定文件

Java代碼為:

    public static void request(String url) {

        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(SSLConnectionSocketFactory.getSystemSocketFactory()).build();

        HttpGet get = new HttpGet(url);
        System.out.println("trying to execute request");
        try (CloseableHttpResponse response = httpClient.execute(get)) {
            HttpEntity entity = response.getEntity();
            System.out.println(response.getStatusLine().getStatusCode());
            String s = EntityUtils.toString(entity);
            System.out.println(s);
            System.out.println("finished request");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
java -Djavax.net.ssl.keyStore=./vendor.my.p12 -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.keyStoreType=PKCS12 -Djavax.net.ssl.trustStore=./ca-server.jks -Djavax.net.ssl.trustStorePassword=changeit  -jar https_test.jar 

 

其中只需要指定好各個文件的位置、密碼以及類型,即可通過 SSLConnectionSocketFactory.getSystemSocketFactory() 實現

 

3.3 在 JVM LIB 中添加密鑰

我們可以直接在 Java 中導入 ca-chain.cert.pem ,服務器的公鑰,通過如下命令

不推薦

sudo keytool -import -alias log-collect-jh.bs58i.baishancdnx.com -keystore cmycerts -file /{your_path}/ca-server.cert.pem

 

使用方法同 #3.2

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM