这里写目录标题
DotNetty TLS 开启双向认证加密传输数据
DotNetty为服务间通讯,包括提供服务的服务器端和请求数据的客户端。如果需要密文传输数据则需要开启TLS,用于通讯加密。TLS涉及到的是证书,首先来看看如果生成DotNetty的TLS证书。
一、生成PFX证书
SSL/TLS证书格式(X.509)分为PEM
- Privacy Enhanced Mail、DER
- Distinguished Encoding Rules
- PEM
Apache和Nginx服务器偏向于使用这种编码格式,内容是BASE64编码,"-----BEGIN…"开始, "-----END…"结束; - DER
Java和Windows服务器偏向于使用这种编码格式,二进制格式,不可读(cat打开有惊喜)。window为PFX/P12(pfx/p12),包含公钥和私钥的二进制格式证书,Java为JKS(Java Key Storage)。
Netty使用的是JKS,可以由keytool将pfx证书转为jks,或者keytool生成jks证书;
DotNetty使用的是PFX,用OpenSSL生成,原理和详细说明看上篇OpenSSL 生成pfx,以下节选linux下生成pfx证书脚本:
# 生成私钥
openssl genrsa -aes256 -passout "pass:yangyiquan" -out key.pem 4096
# 生成公钥
openssl req -new -x509 -days 3650 -key key.pem -passin "pass:yangyiquan" -out cert.csr -subj "/C=CN"
# 打包为pfx
openssl pkcs12 -export -in cert.csr -inkey key.pem -out dotnett.linux.pfx
## 输入三次密码:yangyiquan
注:需要将证书拷贝到工程中,服务端、客户端都要,并且在证书属性中将‘生成操作’改为‘内容’,‘复制到输出目录’改为‘如果较新则复制’
二、服务器端
2.1 引用Nuget:
- DotNetty.Codecs
- DotNetty.Handlers
- DotNetty.Transport.Libuv
- DotNetty.Handlers
- DotNetty.Transport
展示DotNetty项目的演示代码SecureChat.Server
,有改动删除了一些依赖:
2.2 创建处理请求类
SecureChatServerHandler.cs
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace SecureChat.Server
{
using System;
using System.Net;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Groups;
public class SecureChatServerHandler : SimpleChannelInboundHandler<string>
{
static volatile IChannelGroup group;
public override void ChannelActive(IChannelHandlerContext contex)
{
IChannelGroup g = group;
//单例 保证只有一个Group
if (g == null)
{
lock (this)
{
if (group == null)
{
g = group = new DefaultChannelGroup(contex.Executor);
}
}
}
contex.WriteAndFlushAsync(string.Format("Welcome to {0} secure chat server!\n", Dns.GetHostName()));
g.Add(contex.Channel);
}
//删了业务类:EveryOneBut
//这里 实际用的时候改为了 ChannelRead,读取请求信息并进行处理
protected override void ChannelRead0(IChannelHandlerContext contex, string msg)
{
//send message to all but this one
string broadcast = string.Format("[{0}] {1}\n", contex.Channel.RemoteAddress, msg);
string response = string.Format("[you] {0}\n", msg);
//处理业务
contex.WriteAndFlushAsync(response);
if (string.Equals("bye", msg, StringComparison.OrdinalIgnoreCase))
{
contex.CloseAsync();
}
}
//读取请求数据完毕
public override void ChannelReadComplete(IChannelHandlerContext ctx) => ctx.Flush();
//当客户端断开时触发该方法,用于关闭连接
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception e)
{
Console.WriteLine("{0}", e.StackTrace);
ctx.CloseAsync();
}
public override bool IsSharable => true;
}
}
2.3 注册DotNetty监听服务
Program.cs
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace SecureChat.Server
{
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Codecs;
using DotNetty.Handlers.Logging;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
//using Examples.Common;
class Program
{
static async Task RunServerAsync()
{
ExampleHelper.SetConsoleLogger();
var bossGroup = new MultithreadEventLoopGroup(1);
var workerGroup = new MultithreadEventLoopGroup();
var STRING_ENCODER = new StringEncoder();
var STRING_DECODER = new StringDecoder();
var SERVER_HANDLER = new SecureChatServerHandler();
X509Certificate2 tlsCertificate = null;
//注释 示例代码的依赖
//if (ServerSettings.IsSsl)
//{
//tlsCertificate = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
//改为 dotnetty.linux.pfx证书为第一步生成,yangyiquan证书密码
tlsCertificate = new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "dotnetty.linux.pfx"), "yangyiquan");
//}
try
{
var bootstrap = new ServerBootstrap();
bootstrap
.Group(bossGroup, workerGroup)
.Channel<TcpServerSocketChannel>()
.Option(ChannelOption.SoBacklog, 100)
.Handler(new LoggingHandler(LogLevel.INFO))
.ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
if (tlsCertificate != null)
{
pipeline.AddLast(TlsHandler.Server(tlsCertificate));
}
pipeline.AddLast(new DelimiterBasedFrameDecoder(8192, Delimiters.LineDelimiter()));
pipeline.AddLast(STRING_ENCODER, STRING_DECODER, SERVER_HANDLER);
}));
IChannel bootstrapChannel = await bootstrap.BindAsync(ServerSettings.Port);
Console.ReadLine();
await bootstrapChannel.CloseAsync();
}
finally
{
Task.WaitAll(bossGroup.ShutdownGracefullyAsync(), workerGroup.ShutdownGracefullyAsync());
}
}
static void Main() => RunServerAsync().Wait();
}
}
三、客户端端
3.1 引用Nuget
3.2 响应处理
展示DotNetty项目的演示代码SecureChat.Client
,有改动删除了一些依赖
客户端发起请求到服务器端,服务器端处理完毕后发送数据到客户端,客户端需要接收并处理。
SecureChatClientHandler.cs
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace SecureChat.Client
{
using System;
using DotNetty.Transport.Channels;
public class SecureChatClientHandler : SimpleChannelInboundHandler<string>
{
//接收到数据,并处理业务 注意改为 ChannelRead,
protected override void ChannelRead0(IChannelHandlerContext contex, string msg) => Console.WriteLine(msg);
//断开连接会触发该方法
public override void ExceptionCaught(IChannelHandlerContext contex, Exception e)
{
Console.WriteLine(DateTime.Now.Millisecond);
Console.WriteLine(e.StackTrace);
contex.CloseAsync();
}
}
}
3.3 连接到服务端
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace SecureChat.Client
{
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Codecs;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
// using Examples.Common;
class Program
{
static async Task RunClientAsync()
{
ExampleHelper.SetConsoleLogger();
var group = new MultithreadEventLoopGroup();
X509Certificate2 cert = null;
string targetHost = null;
//注释 示例代码的依赖
//if (ClientSettings.IsSsl)
//{
//cert = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
//改为 dotnetty.linux.pfx证书为第一步生成,yangyiquan证书密码
cert = new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "dotnetty.linux.pfx"), "yangyiquan");
targetHost = cert.GetNameInfo(X509NameType.DnsName, false);
//}
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(group)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
if (cert != null)
{
pipeline.AddLast(new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(targetHost)));
}
pipeline.AddLast(new DelimiterBasedFrameDecoder(8192, Delimiters.LineDelimiter()));
//注册 接收服务端数据,并处理
pipeline.AddLast(new StringEncoder(), new StringDecoder(), new SecureChatClientHandler());
}));
IChannel bootstrapChannel = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port));
//模拟发送消息到服务端
for (;;)
{
string line = Console.ReadLine();
if (string.IsNullOrEmpty(line))
{
continue;
}
try
{
//发送数据给服务端,即发起一次RPC
await bootstrapChannel.WriteAndFlushAsync(line + "\r\n");
}
catch
{
}
//断开与服务器的连接,并结束当前程序
if (string.Equals(line, "bye", StringComparison.OrdinalIgnoreCase))
{
await bootstrapChannel.CloseAsync();
break;
}
}
await bootstrapChannel.CloseAsync();
}
finally
{
group.ShutdownGracefullyAsync().Wait(1000);
}
}
static void Main() => RunClientAsync().Wait();
}
}
以上为DotNetty使用的完整过程,如果不用TLS将相关的代码删除即可。
DotNetty项目地址:
https://github.com/Azure/DotNetty