虽然我们在内部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:\security\server>keytool -genkey -alias securechat -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore sChat.jks
D:\security\server>keytool -export -alias securechat -keystore sChat.jks -storepass sNetty -file sChat.cer
存储在文件 中的证书
D:\security\server>cd /d ../client
D:\security\client>keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass cNetty -storepass cNetty -keystore cChat.jks
D:\security\client>keytool -import -trustcacerts -alias securechat -file ../server\sChat.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 {
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 {
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;
}
}