欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Netty实现SSL双向验证完整实例

程序员文章站 2022-07-13 14:58:01
...

 一、证书准备

     要使用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);
			}
		}
	}


}

 测试通过,搞了因为在网上没有找到完整的实例,所以因为一个小问题,找了两天都没有找到原因,希望看到的同学能够有所收获