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,则安装的安全提供程序将选择优先级最高的实现。

 

 

原文地址:https://www.cnblogs.com/fubinhnust/p/9944461.html