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
- SslEngine实例已配置并初始化
- SslHandler实例存储在会话中
- 除非特别要求,否则我们启动握手(在客户端和服务器端具有不同的含义:客户端将发送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,则安装的安全提供程序将选择优先级最高的实现。