1 Introduction
資料安全在網路通訊中是非常重要的一個方面。為了支援 SSL/TLS,Java 提供了 javax.net.ssl包下的類SslContext 和 SslEngine 。在Netty架構下,I/O資料在ChannelPipeline中被管道中的ChannelHandler處理並轉寄給下一個ChannelHandler。自然而然地,Netty也提供了ChannelHandler的實現SslHandler來支援SSL, 有一個內部 SslEngine 做實際的工作。
2 Steps
首先看看SslHandler的建構函式:
public SslHandler(SSLEngine engine) {
this(engine, false);
}
public SslHandler(SSLEngine engine, boolean startTls) {
this(engine, startTls, ImmediateExecutor.INSTANCE);
}
不難發現,我們需要一個SSLEngine對象來構建SslHandler。根據資料可以知道,需要根據已初始化的 SSLContext 來調用 SSLContext.createSSLEngine() 即可建立 SSLEngine。
所以基於Netty架構的在TCP串連中使用SSL/TLS加密的流程如下:
1.在代碼中匯入認證,並使用該認證構造SSLContext
2.調用SSLContext對象的createSSLEngine()建立 SSLEngine
3.用SSLEngine對象去初始化Netty的SslHandler
4.在Netty的ChannelInitializer.initChannel()中,往管道(pipeline)中安裝SslHandler。
3 Usage
3.1 匯入認證
public SSLContext getClientSSLContext(){
KeyStore trustKeyStore= KeyStore.getInstance("JKS");// 訪問Java密鑰庫,JKS是keytool建立的Java密鑰庫
InputStream keyStream = MyApplication.getAppContext().getAssets().open("key.jks");//開啟認證檔案(.jks格式)
char keyStorePass[]="12345678".toCharArray(); //認證密碼
keyStore .load(trustKeyStore,keyStorePass);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore );//儲存服務端的授權認證
SSLContext clientContext = SSLContext.getInstance( "TLS");
clientContext.init(null, trustManagerFactory.getTrustManagers(), null);
return clientContext;
}
在上述代碼,只是實現了client採用trustKeyStore中的key.jks認證(包含了server的公開金鑰)對資料解密,如果解密成功,證明訊息來自server,進行邏輯處理。
僅僅是server->client單向的SSL認證。
如果要實現server和client之間雙向的身份認證,需要模仿trustManagerFactory的初始化來構建一個KeyManagerFactory來其中儲存用戶端的私密金鑰,並傳入
clientContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
具體參照 SSL雙向認證java實現
3.2 添加 SslHandler
ChannelHandler在應該初始化(ChannelInitializer.initChannel()被調用時)階段被安裝在ChannelPipeline中。
Bootstrap的介紹參考Android開發之使用Netty進行Socket編程(二)
Boostrap的初始化參考Android開發之使用Netty進行Socket編程(三)
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine engine = getClientSSLContext().createSSLEngine();
engine.setUseClientMode(true);
pipeline.addFirst("ssl", new SslHandler(engine));
//....再添加其他的ChannelHandler
pipeline.addLast(nettyChannelHandler);
}
});
在大多數情況下,SslHandler 將成為 ChannelPipeline 中的第一個 ChannelHandler,所以調用了pipeline.addFirst() 。這將確保所有其他 ChannelHandler 應用他們的邏輯到資料後加密後才發生,從而確保他們的變化是安全的