Skip to content

【Netty4.x教程】Netty 开启SSL支持 #17

@TFdream

Description

@TFdream

生成自签证书

1、生成自签证书

可以参考这篇 使用keytool工具生成证书

关于keytool的官方说明:https://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html

2、客户端导入证书

对于自签证书 需要在客户端进行导入,导入证书命令如下:

keytool -import -trustcacerts -alias netty -keystore $JAVA_HOME/jre/lib/security/cacerts -file nginx.crt -storepass changeit 

例如:

RickydeMBP:netty-in-action apple$ sudo keytool -import -trustcacerts -alias netty -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts -file /var/folders/_y/q3j1y2fn6996rzfd8_50xzjm0000gn/T/keyutil_example.com_2847488297227795850.crt -storepass changeit

会提示 是否信任此证书? 输入 y 即可。控制台输出如下:

所有者: CN=example.com
发布者: CN=example.com
序列号: 9ac1b92fa3e2812
有效期为 Sat Jun 01 10:58:41 CST 2019 至 Sat Jan 01 07:59:59 CST 10000
证书指纹:
	 MD5:  92:5C:7A:F6:56:F5:04:6C:AF:D3:AD:1D:09:B0:3E:E1
	 SHA1: 59:29:5E:19:BE:B7:9D:4F:FB:96:0B:5E:A8:F8:7F:1C:19:0F:BB:D3
	 SHA256: AC:7E:DB:8F:84:16:A5:49:CE:C2:BB:CD:A2:E9:C5:1B:F1:38:C6:9D:FA:51:E6:82:34:FB:66:52:35:9C:53:16
签名算法名称: SHA256withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3
是否信任此证书? [否]:  y
证书已添加到密钥库中

删除:

keytool -delete -alias netty -keystore $JAVA_HOME/jre/lib/security/cacerts  -storepass changeit 

Netty开启SSL

Netty中提供了io.netty.handler.ssl.SslHandler类,使用起来非常便捷。

本文中使用的Netty版本为 4.1.42.Final,不同版本可能略有不同。

Server端

代码如下:

    public void run() throws Exception {

        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        UserAuthHandler userAuthHandler = new UserAuthHandler();

        //SSL
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        System.out.println(ssc.certificate());
        System.out.println(ssc.privateKey());

        SslContext sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
                .build();

        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (3)
                    .handler(new LoggingHandler(LogLevel.INFO)) //增加LOG
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() { // (4)
                        @Override
                        public void initChannel(NioSocketChannel ch) throws Exception {
                            //pipeline
                            ChannelPipeline pipeline = ch.pipeline();

                            //增加LOG
                            pipeline.addLast("loggingHandler", new LoggingHandler(LogLevel.INFO));

                            //空闲检测
                            pipeline.addLast("idleCheckHandler", new ServerIdleCheckHandler());

                            //SSL
                            SslHandler sslHandler = sslContext.newHandler(ch.alloc());
                            pipeline.addLast("sslHandler", sslHandler);

                            //注意:顺序不能错
                            //handler的顺序:读保证自上而下,写保证自下而上就行了,读与写之间其实顺序无所谓,但是一般为了好看对称,我们是一组一组写。
                            pipeline.addLast("orderFrameDecoder", new OrderFrameDecoder());
                            pipeline.addLast("orderFrameEncoder", new OrderFrameEncoder());

                            pipeline.addLast("orderProtocolEncoder", new OrderProtocolEncoder());
                            pipeline.addLast("orderProtocolDecoder", new OrderProtocolDecoder());

                            //用户身份鉴权
                            pipeline.addLast("userAuthHandler", userAuthHandler);

                            pipeline.addLast("orderProcessHandler", new OrderServerProcessHandler());
                        }
                    });

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)
            LOG.info("点餐系统-服务端, port:{} 启动完成", port);

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

Client端

代码如下:

    public void run() throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        //SSL
        SslContext sslContext = SslContextBuilder.forClient()
                .build();
        try {
            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    //pipeline
                    ChannelPipeline pipeline = ch.pipeline();

                    //增加LOG
                    pipeline.addLast("loggingHandler", new LoggingHandler(LogLevel.INFO));

                    //SSL
                    SslHandler sslHandler = sslContext.newHandler(ch.alloc());
                    pipeline.addLast("sslHandler", sslHandler);

                    //空闲检测
                    pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 15, 0, TimeUnit.SECONDS));

                    //注意:顺序不能错
                    //handler的顺序:读保证自上而下,写保证自下而上就行了,读与写之间其实顺序无所谓,但是一般为了好看对称,我们是一组一组写。
                    pipeline.addLast("orderFrameDecoder", new OrderFrameDecoder());
                    pipeline.addLast("orderFrameEncoder", new OrderFrameEncoder());

                    pipeline.addLast("orderProtocolEncoder", new OrderProtocolEncoder());
                    pipeline.addLast("orderProtocolDecoder", new OrderProtocolDecoder());

                    //心跳检测
                    pipeline.addLast("keepLiveHandler", new ClientKeepLiveHandler());

                    //处理响应结果
                    pipeline.addLast("orderClientHandler", new OrderClientHandler());

                }
            });

            // Start the client.
            ChannelFuture f = b.connect(host, port).sync(); // (5)
            LOG.info("点餐系统-客户端, 连接服务器:{}:{}", host, port);

            //身份鉴权
            Command authCommand = new AuthCommand("admin", "admin");
            RpcRequest authRequest = new RpcRequest(IdUtils.nextRequestId(), authCommand);
            //发送
            f.channel().writeAndFlush(authRequest);

            //1.发送点餐请求
            Long requestId = IdUtils.nextRequestId();
            Command command = new OrderCommand(IdUtils.nextSeatId(), Arrays.asList("鱼香肉丝"));
            RpcRequest request = new RpcRequest(requestId, command);

            //2.写入
            f.channel().writeAndFlush(request);
            LOG.info("点餐系统-客户端, 向服务器:{}:{} 发送点餐请求requestId:{}, 请求报文:{}",
                    host, port, requestId, JsonUtils.toJson(request));

            //3.等待响应结果
            CommandResultFuture future = new CommandResultFuture();
            RequestPendingCenter.INSTANCE.add(requestId, future);

            //3.1 获取结果
            CommandResult result = future.get();
            LOG.info("点餐系统-客户端, 收到服务器:{}:{} 请求requestId:{} 响应结果:{}",
                    host, port, requestId, JsonUtils.toJson(result));

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }

相关资料

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions