Netty添加ssl实现https

发表时间:2017-09-13 14:59:28 浏览量( 63 ) 留言数( 0 )

学习目标:

1、了解LengthFieldBasedFrameDecoder

2、了解Netty的编解码


学习过程:

    通过SSL/TLS 保护Netty 应用程序如今,数据隐私是一个非常值得关注的问题,作为开发人员,我们需要准备好应对它。至少我们应该熟悉像SSL和TLS,传输层安全(TLS)协议,这样的安全协议,它们层叠在其他协议之上,用以实现数据安全。我们在访问安全网站时遇到过这些协议,但是它们也可用于其他不是基于HTTP的应用程序,如安全SMTP(SMTPS)邮件服务器甚至是关系型数据库系统。为了支持SSL/TLS,Java 提供了javax.net.ssl 包,它的SSLContext 和SSLEngine类使得实现解密和加密相当简单直接。Netty 通过一个名为SslHandler 的ChannelHandler实现利用了这个API,其中SslHandler 在内部使用SSLEngine 来完成实际的工作。

一、生成证书介绍

    权威的数字证书颁发机构有:GlobalSign,wosign。因为官方生成证书是需要付费的,当然我们也可以通过Java的KeyTool工具自己实现一个签名证书?一般公司可以购买证书。

    SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA(如GlobalSign,wosign),在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。SSL证书通过在客户端浏览器和Web服务器之间建立一条SSL安全通道。

    HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块,并且会进行身份的验证。

1、使用JDK自带工具KeyTool 生成自签发证书!

keytool密钥和证书管理工具,先看看命令参数:

 -certreq            生成证书请求

 -changealias        更改条目的别名

 -delete             删除条目

 -exportcert         导出证书

 -genkeypair         生成密钥对

 -genseckey          生成密钥

 -gencert            根据证书请求生成证书

 -importcert         导入证书或证书链

 -importpass         导入口令

 -importkeystore     从其他密钥库导入一个或所有条目

 -keypasswd          更改条目的密钥口令

 -list               列出密钥库中的条目

 -printcert          打印证书内容

 -printcertreq       打印证书请求的内容

 -printcrl           打印 CRL 文件的内容

 -storepasswd        更改密钥库的存储口令

使用 "keytool -command_name -help" 获取 command_name 的用法。

第一步:为服务器生成证书

先在D盘建立一个keys目录用户保存生成的相关证书。

命令如下:

keytool -genkey -alias nettyserver  -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore D:/keys/nettyserver.keystore -storepass 123456

操作界面:名字和姓氏应该填写自己的域名,不然还是通不过的。

attcontent/8adb1f47-4b34-42a6-ae6e-41941251ae6d.png

打开D://keys查看是否已经生成了。

第二步:为客户端生成证书

为浏览器生成证书,以便让服务器来验证它。为了能将证书顺利导入至IE和Firefox,证书格式应该是PKCS12,

输入命令:

keytool -genkey -alias client1 -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -storetype PKCS12 -keystore D:/keys/nettyclient.p12 -storepass 123456

输出界面上上面一样的


打开D://keys查看是否已经生成了。


第三步:让服务器信任客户端证书


1、由于不能直接将PKCS12格式的证书库导入,必须先把客户端证书导出为一个单独的CER文件,使用如下命令:

keytool -export -alias client1 -keystore D:/keys/nettyclient.p12 -storetype PKCS12 -keypass 123456 -file D:/keys/nettyclient.cer

注意:Keypass:指定CER文件的密码,但会被忽略,而要求重新输入


2、将该文件导入到服务器的证书库,添加为一个信任证书:

keytool -import -v -file D:/keys/nettyclient.cer -keystore D:/keys/nettyserver.keystore -storepass 123456

完成之后通过list命令查看服务器的证书库,


3、可以看到两个证书,一个是服务器证书,一个是受信任的客户端证书:

keytool -list -v -keystore D:/keys/nettyserver.keystore


第四步:让客户端信任服务器证书


1、由于是双向SSL认证,客户端也要验证服务器证书,因此,必须把服务器证书添加到浏览器的“受信任的根证书颁发机构”。由于不能直接将keystore格式的证书库导入,

必须先把服务器证书导出为一个单独的CER文件,使用如下命令:

keytool -keystore D:/keys/nettyserver.keystore -export -alias nettyserver  -file D:/keys/nettyserver.cer


2、双击server.cer文件,按照提示安装证书,将证书填入到“受信任的根证书颁发机构”。

填入方法:

打开浏览器   - 工具  -  internet选项-内容- 证书-把中级证书颁发机构里的www.localhost.com(该名称即时你前面生成证书时填写的名字与姓氏)证书导出来-再把导出来的证书导入  受信任的根颁发机构  就OK了。

一会我们需要使用浏览器访问netty的服务器中使用的。


attcontent/f3bdbefc-dee9-46dd-b046-71a3804d30e2.png


在讲解Netty使用这个ssl证书之前,事实上很多时候也是可以使用nginx或在tomcat等服务器使用ssl配置成为https服务的,nginx回更多一点,大家可以自己参考一下其他的网站:

从keystore(jks)文件中提取私钥:http://blog.csdn.net/maotongbin/article/details/51064272

配置nginx:http://www.cnblogs.com/shindo/p/6117647.html


二、Netty程序中使用证书

   Netty 的OpenSSL/SSLEngine 实现Netty 还提供了使用OpenSSL 工具包(www.openssl.org)的SSLEngine 实现。这个OpenSsl-Engine 类提供了比JDK 提供的SSLEngine 实现更好的性能。如果OpenSSL库可用,可以将Netty 应用程序(客户端和服务器)配置为默认使用OpenSslEngine。如果不可用,Netty 将会回退到JDK 实现。注意,无论你使用JDK 的SSLEngine 还是使用Netty 的OpenSslEngine,SSL API 和数据流都是一致的。

    在大多数情况下,SslHandler 将是ChannelPipeline 中的第一个ChannelHandler。这确保了只有在所有其他的ChannelHandler 将它们的逻辑应用到数据之后,才会进行加密。SslHandler 具有一些有用的方法,如表11-1 所示。例如,在握手阶段,两个节点将相互验证并且商定一种加密方式。你可以通过配置SslHandler 来修改它的行为,或者SSL/TLS握手一旦完成之后提供通知,握手阶段完成之后,所有的数据都将会被加密。SSL/TLS 握手将会被自动执行。



三、代码示例使用HTTPS

启用HTTPS 只需要将SslHandler 添加到ChannelPipeline 的ChannelHandler 组合中:

ChannelPipeline pipeline = ch.pipeline();

SSLEngine engine = context.newEngine(ch.alloc());

pipeline.addFirst("ssl", new SslHandler(engine));

完整代码如下:

	public void run() throws Exception {
		EventLoopGroup bossGroup = new NioEventLoopGroup(); //
		EventLoopGroup workerGroup = new NioEventLoopGroup();

		KeyManagerFactory keyManagerFactory = null;
		KeyStore keyStore = KeyStore.getInstance("JKS");
		keyStore.load(new FileInputStream("D:/keys/nettyserver.keystore"),"123456".toCharArray());
		//增加属性:algorithm="ibmX509"(因为AIX上采用的JDK是IBM的,需要将该算法属性加上,通常默认的是Sun的算法SunX509
		keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
		keyManagerFactory.init(keyStore, "123456".toCharArray());
		SslContext sslContext = SslContextBuilder.forServer(keyManagerFactory).build();

		try {
			ServerBootstrap b = new ServerBootstrap(); //
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)) //
					.childHandler(new HttpPipelineInitializer(sslContext,false)); //

			// 绑定端口,开始接收进来的连接
			ChannelFuture f = b.bind(port).sync(); //

			// 等待服务器 socket 关闭 。
			// 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}


客户端:

public class HttpClient {
	public static void main(String[] args) throws Exception {

		String host ="localhost";
		int port = 8043;
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		
		TrustManagerFactory tf = null; 
		KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(new FileInputStream("D:/keys/nettyserver.keystore"), "123456".toCharArray());
        tf = TrustManagerFactory.getInstance("SunX509");
        tf.init(keyStore);
        SslContext sslContext = SslContextBuilder.forClient().trustManager(tf).build();

		try {
			Bootstrap b = new Bootstrap(); 
			b.group(workerGroup); 
			b.channel(NioSocketChannel.class); 
			b.option(ChannelOption.SO_KEEPALIVE, true); 
			b.handler(new HttpPipelineInitializer(sslContext,true));

			// 启动客户端
			ChannelFuture f = b.connect(host, port).sync(); // (5)

			// 等待连接关闭
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
		}
	}
}