1. 程式人生 > >Mina Core 11-SSL過濾

Mina Core 11-SSL過濾

SslFilter是負責管理通過安全連線傳送的資料的加密和解密的過濾器。每當您需要建立安全連線或轉換現有連線以使其安全時,您必須在過濾器鏈中新增SslFilter。

由於任何會話都可以隨意修改它的訊息過濾器鏈,因此它允許在開啟的連線上使用startTLS等協議。

請注意,雖然名稱包含SSL,但SslFilter支援TLS。實際上,TLS應該已經取代了SSL,但由於歷史原因,SSL仍然被廣泛使用。

基本用法

如果您希望您的應用程式支援SSL / TLS,只需在您的鏈中新增SslFilter:

DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
SslFilter sslFilter = new SslFilter(sslContext);
chain.addFirst("sslFilter", sslFilter);

  

你顯然也需要一個SslContext例項:

SSLContext sslContext;
 
    try
    {
        // Initialize the SSLContext to work with our key managers.
        sslContext = SSLContext.getInstance( "TLS" );
        sslContext.init( ... ); // Provide the needed KeyManager[], TrustManager[] and SecureRandom instances
    }
    catch ( Exception e )
    {
        // Handle your exception
    }

  

這取決於您提供KeyManager,TrustManager和SecureRandom例項。

一定要在鏈條的第一個位置注入SslFilter!

稍後我們將看到有關如何建立SSLContext的詳細示例。

一點理論

如果您想更深入地瞭解它是如何工作的,請閱讀以下段落......

SSL基礎知識

我們不打算解釋SSL是如何工作的,有很好的書籍。我們將簡要介紹它如何工作以及如何在MINA中實施。

首先,您必須瞭解SSL / TLS是RFC中定義的協議:TLS 1.0,TLS 1.1和TLS 1.2。正在制定TLS 1.3草案......

它最初是由Netscape開發的,在成為TLS之前命名為SSL(從1.0到3.0)。如今,SSL 2.0 *和SSL 3.0 **已被棄用,不應使用。

SSL / TLS協議

由於它是一個協議,它需要客戶端和伺服器之間的一些對話。這就是SSL / TLS的內容:描述此對話方塊。

足以知道任何安全交換被稱為握手的否定階段排除,該角色是在客戶端和伺服器之間就將要使用的加密方法達成協議。基本的SSL / TLS會話將是一個看起來像:

 

 

正如您在此圖中所看到的,它是一個兩階段協議:首先是握手,然後在完成時客戶端和伺服器將能夠交換將被加密的資料

握手

基本上,它都是關於用於加密資料的許多元素的否定。詳細資訊在本文件的上下文中並不那麼有趣,足以說許多訊息將在客戶端和伺服器之間交換,並且在此階段不會發送任何資料。

實際上,握手啟動有兩個條件:伺服器必須等待一些握手訊息到達客戶端必須傳送ClientHello訊息

我們使用Java SSLEngine類來管理整個SSL / TLS協議。 MINA應該注意的是會話的當前狀態是能夠獲取和處理客戶端HelloClient訊息。當您在過濾器鏈中注入SslFilter時,會發生以下幾件事:

1.建立了一個SslHandler例項(我們為每個會話建立一個例項)。此SslHandler例項負責整個處理(即將到來的訊息的握手和加密/解密)

2此SslHandler使用已附加到SslFilter的SslContext例項建立SSLEngine

  1. SslEngine例項已配置並初始化
  2. SslHandler例項儲存在會話中
  3. 除非特別要求,否則我們啟動握手(在客戶端和伺服器端具有不同的含義:客戶端將傳送ClientHello訊息,而伺服器切換到等待某些資料被解包的模式)。請注意,如果需要,可以在以後完成握手初始化

 

我們都準備好了。接下來的幾個步驟是純SSL / TLS協議交換。如果呼叫了session.write()方法,則會將訊息排入佇列,等待握手完成。將SslFilter新增到鏈中時,任何掛起的訊息都將導致SSL / TLS握手失敗,因此請確保在要注入它時有一個乾淨的位置。我們也不會收到任何非SSL / TLS協議訊息的訊息。

如果要實現StartTLS,最後一點非常重要:因為它允許您的應用程式隨時從純文字交換切換到加密交換,您必須確保雙方都沒有待處理的訊息。顯然,在客戶端 - 啟動StartTLS的一方 - 每個待處理的訊息都將在傳送StartTLS訊息之前傳送,但它必須阻止任何其他不屬於後續握手的訊息,直到握手完成為止。在伺服器端,一旦收到StartTLS訊息,就不應該向遠端對等體寫入訊息。

事實上,在握手完成之前,在鏈中注入SslFilter應該阻止任何不屬於握手協議的交換。如果您在握手完成之前提交要傳送和加密的訊息,則不會拒絕該訊息,而是排隊並在握手完成後處理該訊息。

之後,傳送的每條訊息都將通過SslHandler例項進行加密,並且每個收到的訊息必須由SslHandler完全解密,然後才能用於下一個過濾器。

傳送資料

         OK,Handshaked已經完成了。您的SslFilter已準備好處理傳入和傳出訊息。讓我們關注你的會話要寫的那些。

    一個重要的事情是你可以在同一個會話中寫一個以上的訊息(如果你的鏈中有一個Executor)。問題是SSLEngine一次不能處理多個訊息。我們需要序列化正在寫出的訊息。更糟糕的是:您無法同時處理傳入的訊息和傳出訊息。

    總而言之,SSL / TLS處理就像一個黑盒子,只接受一個輸入,在完成任務之前無法處理任何事情。以下模式表示它對傳出訊息的工作方式。

   

 

傳入訊息並沒有那麼不同,除了我們在IoProcessor和SslFilter之間沒有Executor。這使事情變得更簡單,除了一件重要的事情發生:當我們處理傳入的訊息時,我們不能再處理外出訊息了。請注意,它也適用於其他方式:當處理傳出訊息時,我們無法處理傳入訊息:

 

 

這裡重要的是SslHander一次不能處理多個訊息。

MINA 2中的SSL /TLS

現在,我們將深入探討MINA程式碼。我們將介紹所有過濾操作:

管理:

  • init()
  • destroy()
  • onPreAdd(IoFilterChain, String, NextFilter)
  • onPostAdd(IoFilterChain, String, NextFilter)
  • onPreRemove(IoFilterChain, String, NextFilter)
  • onPostRemove(IoFilterChain, String, NextFilter)

會話事件:

  • sessionCreated(NextFilter, IoSession)
  • sessionOpened(NextFilter, IoSession)
  • sessionClosed(NextFilter, IoSession)
  • sessionIdle(NextFilter, IoSession, IdleStatus)
  • exceptionCaught(NextFilter, IoSession, Throwable)
  • filterClose(NextFilter, IoSession)
  • inputClosed(NextFilter, IoSession)

訊息事件:

  • messageReceived(NextFilter, IoSession, Object)
  • filterWrite(NextFilter, IoSession, WriteRequest)
  • messageSent(NextFilter, IoSession, WriteRequest)

管理

以下是Filter的管理方法:

onPreAdd

這是我們建立SslHandler例項並初始化它的地方。我們還定義了支援的密碼。

SslHandler例項本身將建立一個SSLEngine例項,並使用SslFilter中設定的所有引數對其進行配置:

1.如果這是客戶端或伺服器端

2.當它是伺服器端時,表示我們想要或需要客戶端身份驗證的標誌

3.已啟用密碼的列表

4.已啟用協議的列表

完成後,對該例項的引用將儲存到Session的屬性中。

onPostAdd

這是我們開始握手的地方,如果它沒有明確推遲。這就是這種方法的作用。所有邏輯都由SslHandler實現。

onPreRemove

在這裡,我們停止SSL會話並清理會話(從會話的鏈中刪除過濾器,從會話的屬性中刪除SslHandler例項)。在重新整理任何尚未處理的事件後,Sslhandler例項也被破壞。

會話事件

以下是通過過濾器鏈傳播並由SslFilter處理的事件:

sessionClosed

我們只是銷燬SslHandler例項。

exceptionCaught

當異常是由關閉的會話引起時,我們有一個特殊的任務要繼續:我們必須收集所有訊息,將它們新增到將要傳播的異常中。

filterClose

在這裡,如果啟動了SSL會話,我們需要關閉它。無論如何,我們將事件傳播到鏈中的下一個過濾器。

訊息事件

最後,並非最不重要的是,與訊息相關的三個事件

messageReceived事件

當我們從套接字讀取一些資料時收到此事件。我們必須處理一些極端情況:握手已經完成握手已經啟動但未完成*沒有握手已經開始,並且SslHandler尚未初始化

這三個用例按頻率順序列出。讓我們看看每個用例會發生什麼。

握手已經完成

好!這意味著每個傳入的訊息都封裝在SSL / TLS信封中,並且應該被解密。現在,我們討論的是訊息,但實際上我們接收的位元組可能需要聚合以形成完整的訊息(至少在TCP中)。如果訊息被分段,我們將收到許多緩衝區,當我們收到最後一塊時,我們將能夠完全解密它。請記住,我們在所有過程中都被阻止,這可能會阻止此會話的SslHandler例項很長一段時間......

在任何情況下,每個資料塊都由SslHandler處理,SslHandler將其收到的位元組的解密委託給SslEngine。

這是我們在messageReceived()中實現的基本演算法:

get the session's sslHandler
 
syncrhonized on sshHandler {
    if handshake completed
        then
            get the sslHandler decrypting the data
            if the application buffer is completed, push it into the message to forward to the IoHandler
        else
            enqueue the incoming data
}
 
flush the messages if any

  

這裡的重要部分是SslHandler將累積資料,直到它有一個完整的訊息進入鏈。這可能需要一段時間,並且許多套接字讀取。原因是SSLEngine無法處理訊息,除非它具有完全解碼訊息的所有位元組。

提示:增加傳輸緩衝區大小以限制傳送大訊息所需的往返次數。

握手尚未完成

這意味著接收的訊息是握手協議的一部分。沒有任何東西會傳播到IoHandler,訊息將由SslHandler使用。

在完成全部握手之前,每個傳入的資料都將被視為握手protocl訊息。

同時,IoHandler將被排隊的訊息,等待Handshake完成。

這是一個模式,表示在兩次往返中收到資料時的完整過程:

 

 

filterwWrite事件

呼叫IoSession.write()方法時將處理此事件。

如果未啟動SSL會話,我們只是累積要寫入的訊息。它將在稍後傳送。

對於一些非常具體的需求,這裡有一個棘手的引數。通常,在實現startTLS協議時,伺服器通過應用程式訊息(可能是響應)從非安全連線切換到安全連線,我們需要在SslFilter之前將響應傳送回客戶端已安裝(否則,響應將被阻止,安全連線的安裝將失敗)。這是DISABLE_ENCRYPTION_ONCE屬性。它包含的內容並不重要(它可以只是一個布林值),這個引數在第一個訊息的會話中出現就足以通過分配SslFilter。

我們控制會話屬性中DISABLE_ENCRYPTION_ONCE標誌的存在,如果存在,我們將其從會話中刪除,並將未加密的訊息推送到要傳送的訊息佇列中。

    否則,如果握手尚未完成,我們將訊息保留在佇列中,如果已完成,我們對其進行加密並安排將其寫入。

如果某個訊息已被安排寫入,我們將它們全部清除。

SSLContext初始化

我們看到,為了建立SSL會話,我們需要建立一個SSLContext。這是程式碼:

SSLContext sslContext;
 
try
{
    // Initialize the SSLContext to work with our key managers.
    sslContext = SSLContext.getInstance( "TLS" );
    sslContext.init( ... ); // Provide the needed KeyManager[], TrustManager[] and SecureRandom instances
}
catch ( Exception e )
{
    // Handle your exception
}

  

我們這裡沒有公開的是建構函式和init()方法。

SSLContext可以通過其建構函式顯式建立 - 或者我們要求靜態工廠返回一個例項(這是我們在前面的程式碼中所做的。第二種方法非常簡單,大部分時間都適合。它足以傳遞要使用的協議的名稱,它是以下之一:

  • SSL
  • SSLv2
  • SSLv3
  • TLS
  • TLSv1
  • TLSv1.1
  • TLSv1.2 (not supported in Java 6)

如果您的客戶支援,強烈建議選擇更高的演算法(即TLSv1.2)。

init()方法有3個引數:

1.一個KeyManager(可以為null)

2.一個TrustManager(可以為null)

3.隨機生成器(可以為null)

如果引數設定為null,則安裝的安全提供程式將選擇優先順序最高的實現。