本文為博主原創,未經允許不得轉載:
zookeeper 作為注冊中心或服務發現協調中心的時候,zookeeper 默認與其他服務通過 http 進行通信。
zookeeper 與協調服務配置 ssl 雙向認證,即 client 端驗證 server 端證書,server 端驗證 client 端證書,
1. ssl 雙向認證過程

1、客戶端向服務器發送連接請求(SSL協議版本號、加密算法種類、隨機數等信息)
2、服務器給客戶端返回服務器端的證書,即公鑰證書,同時也返回證書相關信息(SSL協議版本號、加密算法種類、隨機數等信息)
3、客戶端使用服務端返回的信息驗證服務器的合法性(首先檢查服務器發送過來的證書是否是由自己信賴的CA中心所簽發的,再比較證書里的消息,例如域名和公鑰,與服務器剛剛發送的相關消息是否一致,如果是一致的,客戶端認可這個服務端的合法身份),驗證通過后,則繼續進行通信,否則終止通信,具體驗證內容包括:
a、證書是否過期
b、發行服務器證書的CA是否可靠
c、返回的公鑰是否能正確解開返回證書中的數字簽名
d、服務器證書上的域名是否和服務器的實際域名相匹配
4、服務端要求客戶端發送客戶端的證書,客戶端會將自己的證書發送至服務端
5、驗證客戶端的證書,通過驗證后,會獲得客戶端的公鑰
6、客戶端向服務器發送自己所能支持的對稱加密方案,供服務器端進行選擇
7、服務器端在客戶端提供的加密方案中選擇加密程度最高的加密方式
8、將加密方式通過使用之前獲取到的公鑰(客戶的公鑰)進行加密,返回給客戶端
9、客戶端收到服務端返回的加密方案密文后,使用自己的私鑰進行解密,獲取具體加密方式,而后獲取該加密方式的隨機碼,用作加密過程中的密鑰,使用之前從服務端證書中獲取到的公鑰進行加密后,發送給服務端
10、服務端收到客戶端發送的消息后,使用自己的私鑰進行解密,獲取對稱加密的密鑰,在接下來的會話中,服務器和客戶端將會使用該密碼進行對稱加密,保證通信過程中信息的安全
2. Zookeeper 配置雙向認證:
bin/zkServer.sh 配置:
export SERVER_JVMFLAGS=” -Dzookeeper.serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory -Dzookeeper.ssl.keyStore.location=/root/zookeeper/ssl/testKeyStore.jks -Dzookeeper.ssl.keyStore.password=testpass -Dzookeeper.ssl.trustStore.location=/root/zookeeper/ssl/testTrustStore.jks -Dzookeeper.ssl.trustStore.password=testpass”
在 “zoo.cfg”中增加:
secureClientPort=2281
“bin/zkCli.sh”的配置為:
export CLIENT_JVMFLAGS=” -Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty -Dzookeeper.client.secure=true -Dzookeeper.ssl.keyStore.location=/root/zookeeper/ssl/testKeyStore.jks -Dzookeeper.ssl.keyStore.password=testpass -Dzookeeper.ssl.trustStore.location=/root/zookeeper/ssl/testTrustStore.jks -Dzookeeper.ssl.trustStore.password=testpass”
zookeeper 通過 netty 進行服務通信。通過 -Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty 指定 netty 通信。
上面是通過在 bin/zkCli.sh 客戶端 指定通信的安全訪問配置。
如果是 java 客戶端時,可以在 java 服務啟動類中添加 以上的 系統環境變量配置,即可配置生效。
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.demo.mapper") public class DemoApplication { public static void main(String[] args) { System.setProperty("zookeeper.clientCnxnSocket","org.apache.zookeeper.ClientCnxnSocketNetty"); System.setProperty("zookeeper.client.secure","true"); System.setProperty("zookeeper.ssl.keyStore.location","/root/zookeeper/ssl/testKeyStore.jks"); System.setProperty("zookeeper.ssl.keyStore.password","testpass"); System.setProperty("zookeeper.ssl.trustStore.location","/root/zookeeper/ssl/testTrustStore.jks"); System.setProperty("zookeeper.ssl.trustStore.password","testpass"); SpringApplication.run(DemoApplication.class, args); } }
3. zookeeper 客戶端認證源碼:
NettyServerCnxnFactory 類中:
private synchronized void initSSL(ChannelPipeline p, boolean supportPlaintext) throws X509Exception, KeyManagementException, NoSuchAlgorithmException { String authProviderProp = System.getProperty(this.x509Util.getSslAuthProviderProperty()); SslContext nettySslContext; if (authProviderProp == null) { SSLContextAndOptions sslContextAndOptions = this.x509Util.getDefaultSSLContextAndOptions(); nettySslContext = sslContextAndOptions.createNettyJdkSslContext(sslContextAndOptions.getSSLContext(), false); } else { SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); X509AuthenticationProvider authProvider = (X509AuthenticationProvider)ProviderRegistry.getProvider(System.getProperty(this.x509Util.getSslAuthProviderProperty(), "x509")); if (authProvider == null) { LOG.error("Auth provider not found: {}", authProviderProp); throw new SSLContextException("Could not create SSLContext with specified auth provider: " + authProviderProp); } sslContext.init(new X509KeyManager[]{authProvider.getKeyManager()}, new X509TrustManager[]{authProvider.getTrustManager()}, (SecureRandom)null); nettySslContext = this.x509Util.getDefaultSSLContextAndOptions().createNettyJdkSslContext(sslContext, false); } if (supportPlaintext) { p.addLast("ssl", new NettyServerCnxnFactory.DualModeSslHandler(nettySslContext)); LOG.debug("dual mode SSL handler added for channel: {}", p.channel()); } else { p.addLast("ssl", nettySslContext.newHandler(p.channel().alloc())); LOG.debug("SSL handler added for channel: {}", p.channel()); } }
初始化 ssl 通信配置,並加載 X509Util 進行校驗:
private synchronized void initSSL(ChannelPipeline pipeline) throws SSLContextException { if (this.sslContext == null || this.sslEngine == null) { X509Util x509Util = new ClientX509Util(); Throwable var3 = null; try { this.sslContext = x509Util.createSSLContext(ClientCnxnSocketNetty.this.clientConfig); this.sslEngine = this.sslContext.createSSLEngine(this.host, this.port); this.sslEngine.setUseClientMode(true); } catch (Throwable var12) { var3 = var12; throw var12; } finally { if (x509Util != null) { if (var3 != null) { try { x509Util.close(); } catch (Throwable var11) { var3.addSuppressed(var11); } } else { x509Util.close(); } } } } pipeline.addLast("ssl", new SslHandler(this.sslEngine)); ClientCnxnSocketNetty.LOG.info("SSL handler added for channel: {}", pipeline.channel()); }
