netty+證書認證

netty客戶端:

package com.lyf.csdn.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 客戶端:啓動時發送1,服務器返回該整數的平方,添加一個心跳,每隔一段時間發送一個隨機數
 *
 * @author xuanchi.lyf
 */
public class NettyClient {

    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);

    public static void main(String[] args) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(5);
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    IdleStateHandler idleStateHandler = new IdleStateHandler(20, 20, 20,
                        TimeUnit.SECONDS);
                    ch.pipeline().addLast(idleStateHandler);
                    ch.pipeline().addLast(new HeartBeatHandler());
                    ch.pipeline().addLast("decoder", new NettyDecoder());
                    ch.pipeline().addLast("encoder", new NettyEncoder());
                    ch.pipeline().addLast(bizGroup, new ClientHandler());
                }
            });
            ChannelFuture f = b.connect("127.0.0.1", 9876).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        }
        logger.info("netty tcp connection build success.");
    }

}

/**
 * 心跳發送[0000]
 */
class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER_IDLE) {
                ctx.channel().writeAndFlush(new Random().nextInt(100));
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

}

class ClientHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        logger.info("channel active");
        ctx.channel().writeAndFlush(1);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("channel inActive");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        logger.info("message = {}.", msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("throw exception", cause);
    }
}

netty服務器端:

package com.lyf.csdn.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;

/**
 * 服務器端
 *
 * @author xuanchi.lyf
 */
public class NettyServer {

    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(50);
        try {
            ServerBootstrap b = new ServerBootstrap().group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
                .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.ALLOW_HALF_CLOSURE, Boolean.FALSE)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast("decoder", new NettyDecoder());
                        channel.pipeline().addLast("encoder", new NettyEncoder());
                        channel.pipeline().addLast(bizGroup, new BizHandler());
                    }
                });
            ChannelFuture f = b.bind(9876).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

}

class NettyDecoder extends LengthFieldBasedFrameDecoder {

    NettyDecoder() {
        super(1024, 0, 4);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) {
            return null;
        }
        int dataLength = frame.readInt();
        if (dataLength == 0) {
            return null;
        }
        return frame.readInt();
    }
}

class NettyEncoder extends MessageToByteEncoder<Serializable> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) {
        out.writeInt(4);
        out.writeInt((Integer) msg);
    }

}

class BizHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(BizHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        logger.info("channel active.");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        int data = (Integer) msg;
        ctx.channel().writeAndFlush(data * data);
        logger.info("message = {}.", msg);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("channel inActive.");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("throw exception", cause);
    }

}

以上我們可以服務器和客戶端建立連接,正常的收發數據了。現在我們需要給netty加上SSL。其中證書可以是單向認證,也可以雙向認證,兩種方式各有特點。

單向認證是在服務器添加證書,客戶端不添加證書,這樣客戶端需要校驗服務器證書,但是服務器不用校驗客戶端證書,這種情況客戶端是安全的,因爲能夠保證服務器是自己人,但是服務器本身不安全,因爲服務器不知道客戶端證書,沒法校驗。這種場景下主要是針對WEB場景。

雙向認證是服務器端同時校驗客戶端身份信息,在點對點通信場景很重要。雙向任務的問題是客戶端證書過期了,如何更換客戶端證書,這個問題很要命。

二、自簽證書

1、生成服務器端和客戶端keystore

keytool -genkey -alias server -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass mypassword  -storepass mypassword -keystore server.jks
keytool -genkey -alias client -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass mypassword  -storepass mypassword -keystore client.jks

其中:

    -alias:表示祕鑰對的名稱

  -keysize:祕鑰長度,1024已經不安全了,至少2048起步

  -keypass、-storepass:密碼,保持一樣就好

  -keystore:生成keystore文件名

2、導出證書

keytool -export -alias server -keystore server.jks -storepass mypassword -file server.cer
keytool -export -alias client -keystore client.jks -storepass mypassword -file client.cer

3、將服務器端證書導入客戶端的keystore(供客戶端驗證服務器證書)

keytool -import -trustcacerts -alias server -file server.cer -storepass mypassword -keystore client.jks

4、將客戶端證書導入服務器端的keystore(供服務器端驗證客戶端證書)

keytool -import -trustcacerts -alias client -file client.cer -storepass mypassword -keystore server.jks

三、單向認證

服務器端代碼:

package com.lyf.csdn.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.security.KeyStore;

/**
 * 服務器端
 *
 * @author xuanchi.lyf
 */
public class NettyServer {

    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    private static SSLEngine    sslEngine;

    static {
        String sChatPath = "/Users/xuanchi.lyf/Desktop/testNetty/server.jks";
        sslEngine = ServerSslContextFactory.getServerContext(sChatPath).createSSLEngine();
        sslEngine.setUseClientMode(false);
    }

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(50);
        try {
            ServerBootstrap b = new ServerBootstrap().group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
                    .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                    .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
                    .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                    .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                    .childOption(ChannelOption.ALLOW_HALF_CLOSURE, Boolean.FALSE)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel channel) {
                            channel.pipeline().addLast("ssl", new SslHandler(sslEngine));
                            channel.pipeline().addLast("decoder", new NettyDecoder());
                            channel.pipeline().addLast("encoder", new NettyEncoder());
                            channel.pipeline().addLast(bizGroup, new BizHandler());
                        }
                    });
            ChannelFuture f = b.bind(9876).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

}

class ServerSslContextFactory {

    private static final String PROTOCOL = "TLS";

    private static SSLContext   SERVER_CONTEXT;

    static SSLContext getServerContext(String pkPath) {
        if (SERVER_CONTEXT != null) {
            return SERVER_CONTEXT;
        }
        InputStream inputStream = null;
        try {
            KeyManagerFactory keyManagerFactory;
            if (pkPath != null) {
                //密鑰庫KeyStore
                KeyStore keyStore = KeyStore.getInstance("JKS");
                //加載服務端證書
                inputStream = new FileInputStream(pkPath);
                //加載服務端的KeyStore  ;sNetty是生成倉庫時設置的密碼,用於檢查密鑰庫完整性的密碼
                keyStore.load(inputStream, "mypassword".toCharArray());
                keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
                //初始化密鑰管理器
                keyManagerFactory.init(keyStore, "mypassword".toCharArray());
                //獲取安全套接字協議(TLS協議)的對象
                SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
                // 初始化此上下文
                // 參數一:認證的密鑰
                // 參數二:對等信任認證
                // 參數三:僞隨機數生成器
                // 由於單向認證,服務端不用驗證客戶端,所以第二個參數爲null
                SERVER_CONTEXT.init(keyManagerFactory.getKeyManagers(), null, null);
            }
        } catch (Exception e) {
            throw new Error("Failed to initialize the server-side SSLContext", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return SERVER_CONTEXT;
    }

}

class NettyDecoder extends LengthFieldBasedFrameDecoder {

    NettyDecoder() {
        super(1024, 0, 4);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) {
            return null;
        }
        int dataLength = frame.readInt();
        if (dataLength == 0) {
            return null;
        }
        return frame.readInt();
    }
}

class NettyEncoder extends MessageToByteEncoder<Serializable> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) {
        out.writeInt(4);
        out.writeInt((Integer) msg);
    }

}

class BizHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(BizHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        logger.info("channel active.");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        int data = (Integer) msg;
        ctx.channel().writeAndFlush(data * data);
        logger.info("message = {}.", msg);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("channel inActive.");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("throw exception", cause);
    }

}

客戶端代碼:

package com.lyf.csdn.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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 io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 客戶端
 *
 * @author xuanchi.lyf
 */
public class NettyClient {

    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);

    private static SSLEngine    sslEngine;

    static {
        String clientKeystorePath = "/Users/xuanchi.lyf/Desktop/testNetty/client.jks";
        sslEngine = ClientSslContextFactory.getClientContext(clientKeystorePath).createSSLEngine();
        sslEngine.setUseClientMode(true);
    }

    public static void main(String[] args) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(5);
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    IdleStateHandler idleStateHandler = new IdleStateHandler(1, 1, 1,
                        TimeUnit.SECONDS);
                    ch.pipeline().addLast("ssl", new SslHandler(sslEngine));
                    ch.pipeline().addLast("idleCheck", idleStateHandler);
                    ch.pipeline().addLast("heartbeat", new HeartBeatHandler());
                    ch.pipeline().addLast("decoder", new NettyDecoder());
                    ch.pipeline().addLast("encoder", new NettyEncoder());
                    ch.pipeline().addLast(bizGroup, new ClientHandler());
                }
            });
            ChannelFuture f = b.connect("127.0.0.1", 9876).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        }
        logger.info("netty tcp connection build success.");
    }

}

class ClientSslContextFactory {

    private static final String PROTOCOL = "TLS";

    private static SSLContext   CLIENT_CONTEXT;

    static SSLContext getClientContext(String caPath) {
        if (CLIENT_CONTEXT != null) {
            return CLIENT_CONTEXT;
        }
        InputStream inputStream = null;
        try {
            //信任庫
            TrustManagerFactory trustManagerFactory = null;
            if (caPath != null) {
                //密鑰庫KeyStore
                KeyStore tks = KeyStore.getInstance("JKS");
                //加載客戶端證書
                inputStream = new FileInputStream(caPath);
                tks.load(inputStream, "mypassword".toCharArray());
                trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
                // 初始化信任庫
                trustManagerFactory.init(tks);
            }
            CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
            //設置信任證書
            TrustManager[] trustManagers = trustManagerFactory == null ? null
                : trustManagerFactory.getTrustManagers();
            CLIENT_CONTEXT.init(null, trustManagers, null);
        } catch (Exception e) {
            throw new Error("Failed to initialize the client-side SSLContext");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return CLIENT_CONTEXT;
    }

}

/**
 * 心跳發送[0000]
 */
class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER_IDLE) {
                ctx.channel().writeAndFlush(new Random().nextInt(100));
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

}

class ClientHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        logger.info("channel active");
        ctx.channel().writeAndFlush(1);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("channel inActive");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        logger.info("message = {}.", msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("throw exception", cause);
    }

}

單向認證,客戶端需要認證服務器身份,此時我們可以構造一個新服務器證書,換上試一下。

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:141)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
    at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1478)
    at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:813)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
    at io.netty.handler.ssl.SslHandler$SslEngineType$3.unwrap(SslHandler.java:292)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1248)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1159)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1194)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
    ... 16 more
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1728)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:304)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:966)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:963)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1416)
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1408)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1316)
    ... 20 more
Caused by: sun.security.validator.ValidatorException: Certificate signature validation failed
    at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:215)
    at sun.security.validator.Validator.validate(Validator.java:260)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:281)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:136)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1501)
    ... 28 more
Caused by: java.security.SignatureException: Signature does not match.
    at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:449)
    at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:392)
    at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:213)
    ... 33 more

說明客戶端驗證了服務器的證書。

四、雙向認證

服務器端:

package com.lyf.csdn.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.security.KeyStore;
import java.security.SecureRandom;

/**
 * 服務器端
 *
 * @author xuanchi.lyf
 */
public class NettyServer {

    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    private static SSLEngine    sslEngine;

    static {
        String sChatPath = "/Users/xuanchi.lyf/Desktop/testNetty/server.jks";
        sslEngine = ServerSslContextFactory.getServerContext(sChatPath, sChatPath)
            .createSSLEngine();
        sslEngine.setUseClientMode(false);
        sslEngine.setNeedClientAuth(true);
    }

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(50);
        try {
            ServerBootstrap b = new ServerBootstrap().group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
                .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
                .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.ALLOW_HALF_CLOSURE, Boolean.FALSE)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast("ssl", new SslHandler(sslEngine));
                        channel.pipeline().addLast("decoder", new NettyDecoder());
                        channel.pipeline().addLast("encoder", new NettyEncoder());
                        channel.pipeline().addLast(bizGroup, new BizHandler());
                    }
                });
            ChannelFuture f = b.bind(9876).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

}

class ServerSslContextFactory {

    private static final String PROTOCOL = "TLS";

    private static SSLContext   SERVER_CONTEXT;

    static SSLContext getServerContext(String pkPath, String caPath) {
        if (SERVER_CONTEXT != null) {
            return SERVER_CONTEXT;
        }
        InputStream inputStream1 = null;
        InputStream inputStream2 = null;
        try {
            KeyManagerFactory keyManagerFactory;
            if (pkPath != null) {
                //密鑰庫KeyStore
                KeyStore keyStore = KeyStore.getInstance("JKS");
                //加載服務端證書
                inputStream1 = new FileInputStream(pkPath);
                //加載服務端的KeyStore  ;sNetty是生成倉庫時設置的密碼,用於檢查密鑰庫完整性的密碼
                keyStore.load(inputStream1, "mypassword".toCharArray());
                keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
                //初始化密鑰管理器
                keyManagerFactory.init(keyStore, "mypassword".toCharArray());
                KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

                //信任庫
                TrustManagerFactory trustManagerFactory = null;
                if (caPath != null) {
                    KeyStore tks = KeyStore.getInstance("JKS");
                    inputStream2 = new FileInputStream(caPath);
                    tks.load(inputStream2, "mypassword".toCharArray());
                    trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
                    trustManagerFactory.init(tks);
                }
                TrustManager[] trustManagers = trustManagerFactory == null ? null
                    : trustManagerFactory.getTrustManagers();

                SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
                SERVER_CONTEXT.init(keyManagers, trustManagers, new SecureRandom());
            }
        } catch (Exception e) {
            throw new Error("Failed to initialize the server-side SSLContext", e);
        } finally {
            if (inputStream1 != null) {
                try {
                    inputStream1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream2 != null) {
                try {
                    inputStream2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return SERVER_CONTEXT;
    }

}

class NettyDecoder extends LengthFieldBasedFrameDecoder {

    NettyDecoder() {
        super(1024, 0, 4);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) {
            return null;
        }
        int dataLength = frame.readInt();
        if (dataLength == 0) {
            return null;
        }
        return frame.readInt();
    }
}

class NettyEncoder extends MessageToByteEncoder<Serializable> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) {
        out.writeInt(4);
        out.writeInt((Integer) msg);
    }

}

class BizHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(BizHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        logger.info("channel active.");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        int data = (Integer) msg;
        ctx.channel().writeAndFlush(data * data);
        logger.info("message = {}.", msg);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("channel inActive.");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("throw exception", cause);
    }

}

客戶端:

package com.lyf.csdn.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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 io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 客戶端
 *
 * @author xuanchi.lyf
 */
public class NettyClient {

    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);

    private static SSLEngine    sslEngine;

    static {
        String clientKeystorePath = "/Users/xuanchi.lyf/Desktop/testNetty/client.jks";
        sslEngine = ClientSslContextFactory.getClientContext(clientKeystorePath, clientKeystorePath)
            .createSSLEngine();
        sslEngine.setUseClientMode(true);
    }

    public static void main(String[] args) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(5);
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    IdleStateHandler idleStateHandler = new IdleStateHandler(1, 1, 1,
                        TimeUnit.SECONDS);
                    ch.pipeline().addLast("ssl", new SslHandler(sslEngine));
                    ch.pipeline().addLast("idleCheck", idleStateHandler);
                    ch.pipeline().addLast("heartbeat", new HeartBeatHandler());
                    ch.pipeline().addLast("decoder", new NettyDecoder());
                    ch.pipeline().addLast("encoder", new NettyEncoder());
                    ch.pipeline().addLast(bizGroup, new ClientHandler());
                }
            });
            ChannelFuture f = b.connect("127.0.0.1", 9876).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        }
        logger.info("netty tcp connection build success.");
    }

}

class ClientSslContextFactory {

    private static final String PROTOCOL = "TLS";

    private static SSLContext   CLIENT_CONTEXT;

    static SSLContext getClientContext(String pkPath, String caPath) {
        if (CLIENT_CONTEXT != null) {
            return CLIENT_CONTEXT;
        }
        InputStream inputStream1 = null;
        InputStream inputStream2 = null;
        try {
            KeyManagerFactory keyManagerFactory;
            if (pkPath != null) {
                //密鑰庫KeyStore
                KeyStore keyStore = KeyStore.getInstance("JKS");
                //加載服務端證書
                inputStream1 = new FileInputStream(pkPath);
                //加載服務端的KeyStore  ;sNetty是生成倉庫時設置的密碼,用於檢查密鑰庫完整性的密碼
                keyStore.load(inputStream1, "mypassword".toCharArray());
                keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
                //初始化密鑰管理器
                keyManagerFactory.init(keyStore, "mypassword".toCharArray());
                KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

                //信任庫
                TrustManagerFactory trustManagerFactory = null;
                if (caPath != null) {
                    KeyStore tks = KeyStore.getInstance("JKS");
                    inputStream2 = new FileInputStream(caPath);
                    tks.load(inputStream2, "mypassword".toCharArray());
                    trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
                    trustManagerFactory.init(tks);
                }
                TrustManager[] trustManagers = trustManagerFactory == null ? null
                    : trustManagerFactory.getTrustManagers();

                CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
                CLIENT_CONTEXT.init(keyManagers, trustManagers, new SecureRandom());
            }
        } catch (Exception e) {
            throw new Error("Failed to initialize the server-side SSLContext", e);
        } finally {
            if (inputStream1 != null) {
                try {
                    inputStream1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream2 != null) {
                try {
                    inputStream2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return CLIENT_CONTEXT;
    }

}

/**
 * 心跳發送[0000]
 */
class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER_IDLE) {
                ctx.channel().writeAndFlush(new Random().nextInt(100));
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

}

class ClientHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        logger.info("channel active");
        ctx.channel().writeAndFlush(1);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("channel inActive");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        logger.info("message = {}.", msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("throw exception", cause);
    }

}

重點理解:CLIENT_CONTEXT.init(keyManagers, trustManagers, new SecureRandom());

第一個參數是是允許對方校驗自己的證書,作用是加密

第二個參數是信任證書列表,作用是鑑權

第三個參數是隨機種子

 

1、最嚴密的策略是雙方互相校驗對方證書,並且只信任對方證書

2、其次可以雙方互相校驗對方證書,並且信任所有證書

當信任所有證書:

new TrustManager[] {new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
    }
    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章