zookeeper ssl 双向认证

经过验证,可在本地localhost 机器上完成服务端-客户端 ssl 双向认证的 脚本(Generate by Google Gimini)

demo :https://github.com/PromiseChan/PromiseChan.github.io/releases/download/zk-ssl-demo-all/zk-ssl.zip

localhost 机器上使用 Java KeyStore(JKS) 创建服务端和客户端双向认证的 X.509 证书,主要分为以下几个步骤:

  1. 创建根证书颁发机构(CA):CA 负责签发服务端和客户端证书。
  2. 为服务端创建密钥库(keystore)和证书签名请求(CSR): 服务端使用自己的私钥生成证书请求。
  3. 使用 CA 签署服务端证书:CA 验证并签署服务端的 CSR,生成服务端证书。
  4. 为客户端创建密钥库(keystore)和证书签名请求(CSR): 客户端也使用自己的私钥生成证书请求。
  5. 使用 CA 签署客户端证书:CA 验证并签署客户端的 CSR,生成客户端证书。
  6. 导入所有证书到相应的信任库(truststore): 服务端和客户端都需要信任对方的证书。

以下是具体的命令行步骤,您可以使用 keytool 这个 Java 自带的工具来完成所有操作。


第一步:创建根证书颁发机构(CA)

  1. 生成 CA 的密钥库和自签名证书

    1
    keytool -genkeypair -alias root-ca -keyalg RSA -keysize 2048 -storetype JKS -keystore ca-keystore.jks -dname "CN=Root CA, OU=Security, O=My Company, L=Beijing, ST=Beijing, C=CN" -validity 3650
    • -alias root-ca:给这个密钥库条目起一个别名。
    • -keyalg RSA:使用 RSA 算法。
    • -keysize 2048:密钥长度为 2048 位。
    • -storetype JKS:指定密钥库类型为 JKS。
    • -keystore ca-keystore.jks:生成的密钥库文件名为 ca-keystore.jks
    • -dname "...":证书所有者信息,CN (Common Name) 建议填写组织名称或身份。
    • -validity 3650:证书有效期,单位为天,这里是 10 年。
  2. 将 CA 的公钥证书导出

    1
    keytool -exportcert -alias root-ca -keystore ca-keystore.jks -file ca.cer
    • -file ca.cer:导出的证书文件,后缀通常为 .cer.crt

第二步:创建服务端证书

  1. 生成服务端的密钥库和私钥

    1
    keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -storetype JKS -keystore server-keystore.jks -dname "CN=localhost, OU=Server, O=My Company, L=Beijing, ST=Beijing, C=CN"
    • 注意:CN 必须是 localhost127.0.0.1,以便在本地运行时匹配主机名。
  2. 生成服务端的证书签名请求(CSR)

    1
    keytool -certreq -alias server -keystore server-keystore.jks -file server.csr
    • -file server.csr:生成的 CSR 文件。

第三步:使用 CA 签署服务端证书

  1. 使用 CA 签署 CSR

    1
    keytool -gencert -alias root-ca -keystore ca-keystore.jks -infile server.csr -outfile server.cer -ext san=dns:localhost,ip:127.0.0.1 -validity 365 -rfc
    • -infile server.csr:输入的 CSR 文件。
    • -outfile server.cer:生成的服务端证书文件。
    • -ext san=dns:localhost,ip:127.0.0.1:添加 Subject Alternative Name (SAN) 扩展,这是现代浏览器和客户端强制要求的,用于指定可信的主机名,这里同时支持 localhost127.0.0.1
    • -validity 365:证书有效期,通常比 CA 证书短。
  2. 将 CA 证书导入服务端的密钥库

    1
    keytool -importcert -alias root-ca -file ca.cer -keystore server-keystore.jks -noprompt
    • 这步是告诉服务端,它需要信任 CA,以便验证客户端证书。
  3. 将服务端自己的证书链导入其密钥库

    1
    keytool -importcert -alias server -keystore server-keystore.jks -file server.cer -noprompt
    • 将刚刚签署的服务端证书导入到自己的密钥库中,使其成为一个完整的私钥-公钥对。

第四步:创建客户端证书

  1. 生成客户端的密钥库和私钥

    1
    keytool -genkeypair -alias client -keyalg RSA -keysize 2048 -storetype JKS -keystore client-keystore.jks -dname "CN=localhost, OU=Client, O=My Company, L=Beijing, ST=Beijing, C=CN"
    • -keystore client-keystore.jks:生成的客户端密钥库。
  2. 生成客户端的证书签名请求(CSR)

    1
    keytool -certreq -alias client -keystore client-keystore.jks -file client.csr

第五步:使用 CA 签署客户端证书

  1. 使用 CA 签署 CSR

    1
    keytool -gencert -alias root-ca -keystore ca-keystore.jks -infile client.csr -outfile client.cer -validity 365 -rfc
  2. 将 CA 证书导入客户端的密钥库

    1
    keytool -importcert -alias root-ca -file ca.cer -keystore client-keystore.jks -noprompt
    • 这步是告诉客户端,它需要信任 CA,以便验证服务端证书。
  3. 将客户端自己的证书链导入其密钥库

    1
    keytool -importcert -alias client -keystore client-keystore.jks -file client.cer -noprompt

第六步:创建信任库

这一步可选,但为了更好的实践,通常会为服务端和客户端单独创建信任库(truststore),只存放信任的公钥。

  1. 为服务端创建信任库

    1
    keytool -importcert -alias client-trust -file client.cer -keystore server-truststore.jks
    • 将客户端的公钥证书导入到服务端的信任库中。
  2. 为客户端创建信任库

    1
    keytool -importcert -alias server-trust -file server.cer -keystore client-truststore.jks
    • 将服务端的公钥证书导入到客户端的信任库中。

总结

现在,您就拥有了以下几个关键文件:

  • **ca-keystore.jks**:CA 的密钥库(包含私钥和证书)。
  • **ca.cer**:CA 的公钥证书。
  • **server-keystore.jks**:服务端的密钥库(包含服务端的私钥和证书)。
  • **client-keystore.jks**:客户端的密钥库(包含客户端的私钥和证书)。
  • **server-truststore.jks**(可选):服务端的信任库,用于存放客户端的证书。
  • **client-truststore.jks**(可选):客户端的信任库,用于存放服务端的证书。

在您的 Java 代码中,您需要配置相应的 JKS 文件路径密码,然后传递给 SSLContext。

希望这些步骤能帮助您顺利配置双向认证!如果您在配置过程中遇到任何问题,或者想了解如何在代码中集成这些证书,随时可以提问。

zoo.cfg 配置

secureClientPort=2281
ssl.quorum=false
ssl.clientAuth=require
serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory

ssl.keyStore.location=/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/server-keystore.jks
ssl.keyStore.password=password
ssl.trustStore.location=/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/server-truststore.jks
ssl.trustStore.password=password

客户端配置

export CLIENT_JVMFLAGS=”-Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty -Dzookeeper.ssl.trustStore.location=/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/client-truststore.jks -Dzookeeper.ssl.trustStore.password=password -Dzookeeper.ssl.keyStore.location=/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/client-keystore.jks -Dzookeeper.ssl.keyStore.password=password -Dzookeeper.client.secure=true”

shell脚本

./zkServer.sh status

java 客户端

package org.example;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.util.List;

public class Main {

public static void main(String[] args) {

    // export CLIENT_JVMFLAGS="-Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty -Dzookeeper.ssl.trustStore.location=/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/client-truststore.jks -Dzookeeper.ssl.trustStore.password=password -Dzookeeper.ssl.keyStore.location=/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/client-keystore.jks  -Dzookeeper.ssl.keyStore.password=password -Dzookeeper.client.secure=true"
    // 1. 设置 SSL/TLS 相关的系统属性
    System.setProperty("zookeeper.client.secure", "true");
    System.setProperty("zookeeper.clientCnxnSocket", "org.apache.zookeeper.ClientCnxnSocketNetty");
    System.setProperty("zookeeper.ssl.trustStore.location", "/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/client-truststore.jks");
    System.setProperty("zookeeper.ssl.trustStore.password", "password");

    // 如果服务端配置了双向认证 (clientAuth=need),则需要配置客户端密钥库
     System.setProperty("zookeeper.ssl.keyStore.location", "/Users/nuopromise/Desktop/code/zookeeper/apache-zookeeper-3.8.4-bin/zookeeper-ssl/tmp/client-keystore.jks");
     System.setProperty("zookeeper.ssl.keyStore.password", "password");

    // 2. 指定 ZooKeeper 服务器地址和端口
    String connectString = "localhost:2281"; // 这里的端口是您在 zoo.cfg 中设置的 secureClientPort
    int sessionTimeout = 5000; // 会话超时时间,单位毫秒

    try {
        // 3. 创建 ZooKeeper 客户端实例
        ZooKeeper zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("start watching ...");
            }
        });

        System.out.println("成功连接到 ZooKeeper!");
        System.out.println("打印 子节点 :");



        // 4. 执行一些操作(例如 ls 查看子节点 )
        // 在实际应用中,您可以在这里进行各种 ZooKeeper 操作
        List<String> childrenList =  zk.getChildren("/",false);
        for (String childPath:childrenList){
            System.out.println(" / 下的子节点: "+childPath);
        }


    } catch (Exception e) {
        System.err.println("连接 ZooKeeper 失败!");
        e.printStackTrace();
    }
}

}

java 验证结果

SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
成功连接到 ZooKeeper!
打印 子节点 :
SLF4J: Failed to load class “org.slf4j.impl.StaticMDCBinder”.
SLF4J: Defaulting to no-operation MDCAdapter implementation.
SLF4J: See http://www.slf4j.org/codes.html#no_static_mdc_binder for further details.
start watching …
/ 下的子节点: zookeeper