Netty实现SSL双向验证完整实例
一、证书准备
要使用ssl双向验证,就必须先要生成服务端和客户端的证书,并相互添加信任,具体流程如下(本人调试这个用例的时候,花了很多时间来验证证书是否正确,以及握手失败的原因,这里证书生成过程只要按流程走,本人能保证绝对没有问题)
现在打开cmd,在哪个目录下打开,证书就会放在哪个目录下:
第一步: 生成Netty服务端私钥和证书仓库命令
keytool -genkey -alias securechat -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore sChat.jks
- -keysize 2048 **长度2048位(这个长度的**目前可认为无法被暴力**)
- -validity 365 证书有效期365天
- -keyalg RSA 使用RSA非对称加密算法
- -dname "CN=localhost" 设置Common Name为localhost
- -keypass sNetty**的访问密码为sNetty
- -storepass sNetty**库的访问密码为sNetty(其实这两个密码也可以设置一样,通常都设置一样,方便记)
- -keystore sChat.jks 指定生成的**库文件为sChata.jks
第二步:生成Netty服务端自签名证书
keytool -export -alias securechat -keystore sChat.jks -storepass sNetty -file sChat.cer
第三步:生成客户端的**对和证书仓库,用于将服务端的证书保存到客户端的授信证书仓库中
keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty -keystore cChat.jks
第四步:将Netty服务端证书导入到客户端的证书仓库中
keytool -import -trustcacerts -alias securechat -file sChat.cer -storepass sNetty -keystore cChat.jks
如果你只做单向认证,则到此就可以结束了,如果是双响认证,则还需继续往下走
第五步:生成客户端自签名证书
keytool -export -alias smcc -keystore cChat.jks -storepass sNetty -file cChat.cer
最后一步:将客户端的自签名证书导入到服务端的信任证书仓库中:
keytool -import -trustcacerts -alias smcc -file cChat.cer -storepass sNetty -keystore sChat.jks
到这里,证书就生成完毕了,我们就可以得到两个jks文件,一个是服务端的sChat.jks ,一个是客户端的cChat.jks ,这两个文件后面初始化sslCOntext的时候会用到
如果还想了解更多可以查看
http://dwj147258.iteye.com/blog/2339934
二、netty服务端
下面就直接贴代码了,首先是实例化SSLContext的类:
package main.java.com.nionetty;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.springframework.core.io.ClassPathResource;
/**
* 初始化sslcontext类
*
*/
public class ContextSSLFactory {
private static final SSLContext SSL_CONTEXT_S ;
private static final SSLContext SSL_CONTEXT_C ;
static{
SSLContext sslContext = null ;
SSLContext sslContext2 = null ;
try {
sslContext = SSLContext.getInstance("SSLv3") ;
sslContext2 = SSLContext.getInstance("SSLv3") ;
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
try{
if(getKeyManagersServer() != null && getTrustManagersServer() != null ){
sslContext.init(getKeyManagersServer(), getTrustManagersServer(), null);
}
if(getKeyManagersClient() != null && getTrustManagersClient() != null){
sslContext2.init(getKeyManagersClient(), getTrustManagersClient(), null);
}
}catch(Exception e){
e.printStackTrace() ;
}
sslContext.createSSLEngine().getSupportedCipherSuites() ;
sslContext2.createSSLEngine().getSupportedCipherSuites() ;
SSL_CONTEXT_S = sslContext ;
SSL_CONTEXT_C = sslContext2 ;
}
public ContextSSLFactory(){
}
public static SSLContext getSslContext(){
return SSL_CONTEXT_S ;
}
public static SSLContext getSslContext2(){
return SSL_CONTEXT_C ;
}
private static TrustManager[] getTrustManagersServer(){
FileInputStream is = null ;
KeyStore ks = null ;
TrustManagerFactory keyFac = null ;
TrustManager[] kms = null ;
try {
// 获得KeyManagerFactory对象. 初始化位默认算法
keyFac = TrustManagerFactory.getInstance("SunX509") ;
is =new FileInputStream( (new ClassPathResource("main/java/conf/sChat.jks")).getFile() );
ks = KeyStore.getInstance("JKS") ;
String keyStorePass = "sNetty" ;
ks.load(is , keyStorePass.toCharArray()) ;
keyFac.init(ks) ;
kms = keyFac.getTrustManagers() ;
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(is != null ){
try {
is.close() ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return kms ;
}
private static TrustManager[] getTrustManagersClient(){
FileInputStream is = null ;
KeyStore ks = null ;
TrustManagerFactory keyFac = null ;
TrustManager[] kms = null ;
try {
// 获得KeyManagerFactory对象. 初始化位默认算法
keyFac = TrustManagerFactory.getInstance("SunX509") ;
is =new FileInputStream( (new ClassPathResource("main/java/conf/cChat.jks")).getFile() );
ks = KeyStore.getInstance("JKS") ;
String keyStorePass = "sNetty" ;
ks.load(is , keyStorePass.toCharArray()) ;
keyFac.init(ks) ;
kms = keyFac.getTrustManagers() ;
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(is != null ){
try {
is.close() ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return kms ;
}
private static KeyManager[] getKeyManagersServer(){
FileInputStream is = null ;
KeyStore ks = null ;
KeyManagerFactory keyFac = null ;
KeyManager[] kms = null ;
try {
// 获得KeyManagerFactory对象. 初始化位默认算法
keyFac = KeyManagerFactory.getInstance("SunX509") ;
is =new FileInputStream( (new ClassPathResource("main/java/conf/sChat.jks")).getFile() );
ks = KeyStore.getInstance("JKS") ;
String keyStorePass = "sNetty" ;
ks.load(is , keyStorePass.toCharArray()) ;
keyFac.init(ks, keyStorePass.toCharArray()) ;
kms = keyFac.getKeyManagers() ;
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(is != null ){
try {
is.close() ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return kms ;
}
private static KeyManager[] getKeyManagersClient(){
FileInputStream is = null ;
KeyStore ks = null ;
KeyManagerFactory keyFac = null ;
KeyManager[] kms = null ;
try {
// 获得KeyManagerFactory对象. 初始化位默认算法
keyFac = KeyManagerFactory.getInstance("SunX509") ;
is =new FileInputStream( (new ClassPathResource("main/java/conf/cChat.jks")).getFile() );
ks = KeyStore.getInstance("JKS") ;
String keyStorePass = "sNetty" ;
ks.load(is , keyStorePass.toCharArray()) ;
keyFac.init(ks, keyStorePass.toCharArray()) ;
kms = keyFac.getKeyManagers() ;
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(is != null ){
try {
is.close() ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return kms ;
}
}
服务端启动类:
package main.java.com.nionetty;
import javax.net.ssl.SSLEngine;
import javax.print.attribute.standard.MediaSize.Engineering;
import main.java.com.nettyTest.SecureChatServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
public class NettySocketServer {
private static SslHandler sslHandler = null ;
private EventLoopGroup bossGroup = null ;
private EventLoopGroup workerGroup = null ;
public void start(){
bossGroup = new NioEventLoopGroup() ;
workerGroup = new NioEventLoopGroup() ;
try{
ServerBootstrap serverStrap = new ServerBootstrap() ;
serverStrap.group(bossGroup , workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000 * 5 * 60)
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pie = socketChannel.pipeline() ;
pie.addLast("decoder" , new MyDecoder()) ;
pie.addLast("encoder" , new MyEncoder()) ;
pie.addLast("handler" , new NettySocketSSLHandler()) ;
SSLEngine engine = ContextSSLFactory.getSslContext().createSSLEngine();
engine.setUseClientMode(false);
engine.setNeedClientAuth(true);
pie.addFirst("ssl", new SslHandler(engine));
}
});
serverStrap.bind(161616).sync() ;
System.out.println("服务已开启");
}catch(Exception e){
e.printStackTrace() ;
bossGroup.shutdownGracefully() ;
workerGroup.shutdownGracefully() ;
}
}
private SslHandler getSslHandler(){
if(sslHandler == null ){
SSLEngine sslEngine = ContextSSLFactory.getSslContext().createSSLEngine() ;
sslEngine.setUseClientMode(false) ;
//false为单向认证,true为双向认证
sslEngine.setNeedClientAuth(true) ;
sslHandler = new SslHandler(sslEngine);
}
return sslHandler ;
}
public static void main(String[] args) {
new NettySocketServer().start() ;
}
}
编码器:
package main.java.com.nionetty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.nio.ByteBuffer;
public class MyEncoder extends MessageToByteEncoder<ByteBuffer>{
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuffer message,
ByteBuf out) throws Exception {
if(message==null){
return;
}
if(message.hasArray()){
byte[] msg =message.array();
if(msg == null || msg.length <= 0){
return;
}
out.writeBytes(msg) ;
}
}
}
解码器:
/*
* Copyright (C) TD Tech<br>
* All Rights Reserved.<br>
*
*/
package main.java.com.nionetty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.nio.ByteBuffer;
import java.util.List;
/**
* Create Date: 2014-11-4 下午02:42:21<br>
* Create Author: lWX232692<br>
* Description :
*/
public class MyDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
List<Object> out) throws Exception {
//UnpooledUnsafeDirectByteBuf(ridx: 0, widx: 1, cap: 1024)
if (buffer != null) {
ByteBuffer msg = null;
try {
if(buffer.readableBytes() > 0 ){
msg = ByteBuffer.allocate(buffer.readableBytes()) ;
byte[] bb = new byte[buffer.readableBytes()] ;
buffer.readBytes(bb) ;
msg.put(bb);
msg.flip();
}
} catch (Exception e) {
e.printStackTrace();
msg = null ;
}
if (msg != null) {
out.add(msg);
}
}
}
}
业务实现类:
package main.java.com.nionetty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
public class NettySocketSSLHandler extends SimpleChannelInboundHandler<ByteBuffer>{
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
// Once session is secured, send a greeting and register the channel to the global channel
// list so the channel received the messages from others.
ctx.pipeline().get(SslHandler.class).handshakeFuture().addListener(
new GenericFutureListener<Future<Channel>>() {
@Override
public void operationComplete(Future<Channel> future) throws Exception {
if(future.isSuccess()){
System.out.println("握手成功");
byte[] array = new byte[]{ (byte)7d, 04} ;
ByteBuffer bu = ByteBuffer.wrap(array) ;
ctx.channel().writeAndFlush(bu) ;
}else{
System.out.println("握手失败");
}
ctx.writeAndFlush(
"Welcome to " + InetAddress.getLocalHost().getHostName() +
" secure chat service!\n");
ctx.writeAndFlush(
"Your session is protected by " +
ctx.pipeline().get(SslHandler.class).engine().getSession().getCipherSuite() +
" cipher suite.\n");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx)
throws Exception {
System.out.println("服务端增加");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx){
System.out.println("移除:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("Unexpected exception from downstream.");
ctx.close();
}
@Override
public void messageReceived(ChannelHandlerContext ctx, ByteBuffer msg) throws Exception {
System.out.println("服务端receive msg ");
byte[] array = new byte[]{00, 01, 00, 00, 00, 06, 05, 03, (byte)7d, 00, 00, 07} ;
ByteBuffer bu = ByteBuffer.wrap(array) ;
ctx.channel().writeAndFlush(bu) ;
}
}
三、客户端
客户端实现类
package main.java.com.nionetty.client;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import javax.net.ssl.SSLEngine;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
import main.java.com.nionetty.ContextSSLFactory;
import main.java.com.nionetty.MyDecoder;
import main.java.com.nionetty.MyEncoder;
public class NettySocketClient {
private EventLoopGroup group ;
private Channel channel = null ;
public void connect(String ip , int port){
group = new NioEventLoopGroup();
try{
Bootstrap strap = new Bootstrap();
strap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE , true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pieple = socketChannel.pipeline() ;
pieple.addLast("decoder" , new MyClientDecoder()) ;
pieple.addLast("encoder" , new MyClientEncoder()) ;
pieple.addLast("handler" , new NettySocketSSLClientHandler()) ;
SSLEngine engine = ContextSSLFactory.getSslContext2().createSSLEngine();
engine.setUseClientMode(true);
pieple.addFirst("ssl", new SslHandler(engine));
}
});
SocketAddress address = new InetSocketAddress(ip, port);
final ChannelFuture future = strap.connect(address).sync();
channel = future.awaitUninterruptibly().channel();
System.out.println("连接成功, channel =" + channel.remoteAddress());
}catch(Exception e ){
e.printStackTrace();
group.shutdownGracefully() ;
}finally{
}
}
private static SslHandler sslHandlerClient = null ;
public static SslHandler getSslHandler(){
if(sslHandlerClient == null){
SSLEngine sslEngine = ContextSSLFactory.getSslContext2().createSSLEngine() ;
sslEngine.setUseClientMode(true) ;
sslHandlerClient = new SslHandler(sslEngine);
}
return sslHandlerClient ;
}
public static void main(String[] args) {
new NettySocketClient().connect("192.168.10.256", 161616) ;
}
}
编码器:
package main.java.com.nionetty.client;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.nio.ByteBuffer;
public class MyClientEncoder extends MessageToByteEncoder<ByteBuffer>{
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuffer message,
ByteBuf out) throws Exception {
if(message==null){
return;
}
if(message .hasArray()){
byte[] msg =message.array();
if(msg == null || msg.length <= 0){
return;
}
out.writeBytes(msg);
}
}
}
解码器:
/*
* Copyright (C) TD Tech<br>
* All Rights Reserved.<br>
*
*/
package main.java.com.nionetty.client;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.nio.ByteBuffer;
import java.util.List;
/**
* Create Date: 2014-11-4 下午02:42:21<br>
* Create Author: lWX232692<br>
* Description :
*/
public class MyClientDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
List<Object> out) throws Exception {
//UnpooledUnsafeDirectByteBuf(ridx: 0, widx: 1, cap: 1024)
if (buffer != null) {
ByteBuffer msg = null;
try {
if(buffer.readableBytes() > 0 ){
msg = ByteBuffer.allocate(buffer.readableBytes()) ;
byte[] bb = new byte[buffer.readableBytes()] ;
buffer.readBytes(bb) ;
msg.put(bb);
msg.flip();
}
} catch (Exception e) {
e.printStackTrace();
msg = null ;
}
if (msg != null) {
out.add(msg);
}
}
}
}
业务handler:
/*
* Copyright (C) TD Tech<br>
* All Rights Reserved.<br>
*
*/
package main.java.com.nionetty.client;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.nio.ByteBuffer;
import java.util.List;
/**
* Create Date: 2014-11-4 下午02:42:21<br>
* Create Author: lWX232692<br>
* Description :
*/
public class MyClientDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
List<Object> out) throws Exception {
//UnpooledUnsafeDirectByteBuf(ridx: 0, widx: 1, cap: 1024)
if (buffer != null) {
ByteBuffer msg = null;
try {
if(buffer.readableBytes() > 0 ){
msg = ByteBuffer.allocate(buffer.readableBytes()) ;
byte[] bb = new byte[buffer.readableBytes()] ;
buffer.readBytes(bb) ;
msg.put(bb);
msg.flip();
}
} catch (Exception e) {
e.printStackTrace();
msg = null ;
}
if (msg != null) {
out.add(msg);
}
}
}
}
测试通过,搞了因为在网上没有找到完整的实例,所以因为一个小问题,找了两天都没有找到原因,希望看到的同学能够有所收获