netty集成ssl完整参考指南(含完整源码)
虽然我们在内部rpc通信中使用的是基于认证和报文头加密的方式实现安全性,但是有些时候仍然需要使用SSL加密,可能是因为对接的三方系统需要,也可能是由于open的考虑。中午特地测了下netty下集成ssl的功能,关于ssl的握手过程以及java安全框架中的相关组件说明,请参考如下链接:
http://www.cnblogs.com/zhjh256/p/6262620.html
http://www.cnblogs.com/zhjh256/p/6104537.html
网上搜了下,并没有看到完整的netty ssl示例例子,netty in action中也只是匆匆带过。特详细的测试和整理如下。
首先生成服务端证书:
D:securityserver>keytool -genkey -alias securechat -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore sChat.jks
D:securityserver>keytool -export -alias securechat -keystore sChat.jks -storepass sNetty -file sChat.cer
存储在文件 <sChat.cer> 中的证书
D:securityserver>cd /d ../client
D:securityclient>keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass cNetty -storepass cNetty -keystore cChat.jks
D:securityclient>keytool -import -trustcacerts -alias securechat -file ../serversChat.cer -storepass cNetty -keystore cChat.jks
所有者: CN=localhost
发布者: CN=localhost
序列号: 78384348
有效期开始日期: Wed Mar 01 12:48:48 CST 2017, 截止日期: Thu Mar 01 12:48:48 CST 2018
证书指纹:
MD5: 94:83:6C:6D:4B:0D:0B:E6:BF:39:B7:2C:17:29:E8:3C
SHA1: 9A:29:27:41:BE:71:38:C8:13:99:3A:8F:C6:37:C2:95:31:14:B4:98
SHA256: E9:31:40:C7:FC:EA:EF:24:54:EF:4C:59:50:44:CB:1F:9A:35:B7:26:07:2D:3B:1F:BC:30:8E:C0:63:45:4F:21
签名算法名称: SHA256withRSA
版本: 3
扩展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 9B 96 0D 50 4A 5E AF 3D 56 25 9C A5 69 C1 3E CC ...PJ^.=V%..i.>.
0010: 32 85 0D A8 2...
]
]
是否信任此证书? [否]: 是
证书已添加到密钥库中
netty服务端源码:
package com.ld.net.spider.server; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.net.InetSocketAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SpiderServerBusiHandler extends SimpleChannelInboundHandler<Object> { static final Logger logger = LoggerFactory.getLogger(SpiderServerBusiHandler.class); @Override protected void channelRead0(final ChannelHandlerContext ctx, final Object msg) throws Exception { System.out.println(msg.toString()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.error("channel " + ((InetSocketAddress)ctx.channel().remoteAddress()).toString() + " exception:",cause); ctx.close(); } }
package com.ld.net.spider.channel; import java.nio.charset.Charset; import javax.net.ssl.SSLEngine; import com.ld.net.spider.server.SpiderServerBusiHandler; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; public class SslChannelInitializer extends ChannelInitializer<Channel> { private final SslContext context; public SslChannelInitializer(SslContext context) { this.context = context; } @Override protected void initChannel(Channel ch) throws Exception { SSLEngine engine = context.newEngine(ch.alloc()); engine.setUseClientMode(false); ch.pipeline().addFirst("ssl", new SslHandler(engine)); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); //最大16M pipeline.addLast("decoder", new StringDecoder(Charset.forName("UTF-8"))); pipeline.addLast("encoder", new StringEncoder(Charset.forName("UTF-8"))); pipeline.addLast("spiderServerBusiHandler", new SpiderServerBusiHandler()); } }
package com.ld.net.spider.channel; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import java.io.FileInputStream; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SocketServerHelper { static final Logger logger = LoggerFactory.getLogger(SocketServerHelper.class); private static int WORKER_GROUP_SIZE = Runtime.getRuntime().availableProcessors() * 2; private static EventLoopGroup bossGroup; private static EventLoopGroup workerGroup; private static Class<? extends ServerChannel> channelClass; public static void startSpiderServer() throws Exception { ServerBootstrap b = new ServerBootstrap(); b.childOption(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.SO_REUSEADDR, true) .childOption(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(false)) .childOption(ChannelOption.SO_RCVBUF, 1048576) .childOption(ChannelOption.SO_SNDBUF, 1048576); bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(WORKER_GROUP_SIZE); channelClass = NioServerSocketChannel.class; logger.info("workerGroup size:" + WORKER_GROUP_SIZE); logger.info("preparing to start spider server..."); b.group(bossGroup, workerGroup); b.channel(channelClass); KeyManagerFactory keyManagerFactory = null; KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("D:\security\server\sChat.jks"), "sNetty".toCharArray()); keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); keyManagerFactory.init(keyStore,"sNetty".toCharArray()); SslContext sslContext = SslContextBuilder.forServer(keyManagerFactory).build(); b.childHandler(new SslChannelInitializer(sslContext)); b.bind(9912).sync(); logger.info("spider server start sucess, listening on port " + 9912 + "."); } public static void main(String[] args) throws Exception { SocketServerHelper.startSpiderServer(); } public static void shutdown() { logger.debug("preparing to shutdown spider server..."); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); logger.debug("spider server is shutdown."); } }
package com.ld.net.spider.channel; import java.net.InetSocketAddress; import java.nio.channels.ClosedChannelException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; public class SocketHelper { static final Logger logger = LoggerFactory.getLogger(SocketHelper.class); public static ChannelFuture writeMessage(Channel channel,String msg) { if(channel!=null){ try { return channel.writeAndFlush(msg).sync(); } catch (Exception e) { String otherInfo = ""; if(channel.remoteAddress() != null) { otherInfo = "remote address [" + ((InetSocketAddress)channel.remoteAddress()).toString() + "]"; } else { otherInfo = "channel is null."; } if(e instanceof ClosedChannelException) { logger.error("channel to " + otherInfo + " is closed",e); } else { logger.error("timeout occured during channel send msg, " + otherInfo,e); } } }else{ logger.error("send msg failed, channel is disconnected or not connect. channel is null, please see caller log."); } return null; } public static ChannelFuture writeMessage(Channel channel,ByteBuf msg) { if(channel!=null){ try { return channel.writeAndFlush(msg).sync(); } catch (Exception e) { logger.error("timeout occured during channel send msg. remote address is:" + ((InetSocketAddress)channel.remoteAddress()).toString(),e); } }else{ logger.error("send msg failed, channel is disconnected or not connect, channel is null, please see caller log."); } return null; } }