Netty SSL双向验证

1. 环境说明

  • 本例使用windows10 + Win64OpenSSL-3_3_0(完整版,不是lite),netty版本4.1.77.Final,JDK-17
  • openssl官方推荐合作下载地址:https://slproweb.com/download/Win64OpenSSL-3_3_0.exe
  • ${openssl_home}是openssl的安装目录
  • 所有命令在${openssl_home}/bin目录下执行
  • windows下openssl的配置文件是${openssl_home}/bin/openssl.cfg,linux下是${openssl_home}/bin/openssl.conf,注意替换后缀名
  • 需要手动按照openssl.cfg的配置创建好各种目录、文件

2. 生成证书

2.1. 创建根证书 密钥+证书

openssl genrsa -des3 -out demoCA/private/ca.key 4096

openssl req -new -x509 -days 3650 -key demoCA/private/ca.key -out demoCA/certs/ca.crt

2.2. 生成请求证书密钥

openssl genrsa -des3 -out demoCA/private/server.key 2048

openssl genrsa -des3 -out demoCA/private/client.key 2048

2.3. 生成csr请求证书

openssl req -new -key demoCA/private/server.key -out demoCA/certs/server.csr -config openssl.cfg

openssl req -new -key demoCA/private/client.key -out demoCA/certs/client.csr -config openssl.cfg

2.4. ca证书对server.csr、client.csr签发生成x509证书

openssl x509 -req -days 3650 -in demoCA/certs/server.csr -CA demoCA/certs/ca.crt -CAkey demoCA/private/ca.key -CAcreateserial -out demoCA/certs/server.crt

openssl x509 -req -days 3650 -in demoCA/certs/client.csr -CA demoCA/certs/ca.crt -CAkey demoCA/private/ca.key -CAcreateserial -out demoCA/certs/client.crt

2.5. 请求证书PKCS#8编码

openssl pkcs8 -topk8 -in demoCA/private/server.key -out demoCA/private/pkcs8_server.key -nocrypt

openssl pkcs8 -topk8 -in demoCA/private/client.key -out demoCA/private/pkcs8_client.key -nocrypt

2.6. 输出文件

server端:ca.crt、server.crt、pkcs8_server.key

client端:ca.crt、client.crt、pkcs8_client.key

3. Java代码

3.1. Server端

  • ServiceMain.java
public class ServiceMain implements CommandLineRunner {
    @Value("${netty.host}")
    private String host;
    @Value("${netty.port}")
    private int port;

    @Resource
    private NettyServer nettyServer;

    public static void main(String[] args) {
        SpringApplication.run(ServiceMain.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(host, port);
        ChannelFuture channelFuture = nettyServer.bind(address);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.destroy()));
        channelFuture.channel().closeFuture().syncUninterruptibly();
    }
}
  • NettyServer.java
package cn.a.service.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;

import javax.annotation.Resource;
import java.io.File;
import java.net.InetSocketAddress;

@Slf4j
@Component("nettyServer")
public class NettyServer {

    private final EventLoopGroup parentGroup = new NioEventLoopGroup();
    private final EventLoopGroup childGroup = new NioEventLoopGroup();
    private Channel channel;

    @Resource
    ApplicationContext applicationContext;

    /**
     * 绑定端口
     *
     * @param address
     * @return
     */
    public ChannelFuture bind(InetSocketAddress address) {
        ChannelFuture channelFuture = null;
        try {
            File certChainFile = ResourceUtils.getFile("classpath:server.crt");
            File keyFile = ResourceUtils.getFile("classpath:pkcs8_server.key");
            File rootFile = ResourceUtils.getFile("classpath:ca.crt");
            SslContext sslCtx = SslContextBuilder.forServer(certChainFile, keyFile)
                    .trustManager(rootFile)
                    .clientAuth(ClientAuth.REQUIRE).build();

            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new NettyChannelInitializer(applicationContext, sslCtx));
            channelFuture = b.bind(address).syncUninterruptibly();
            channel = channelFuture.channel();
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            if (null != channelFuture && channelFuture.isSuccess()) {
                log.info("netty server start done.");
            } else {
                log.error("netty server start error.");
            }
        }
        return channelFuture;
    }


    /**
     * 销毁
     */
    public void destroy() {
        if (null == channel) return;
        channel.close();
        parentGroup.shutdownGracefully();
        childGroup.shutdownGracefully();
    }


    /**
     * 获取通道
     *
     * @return
     */
    public Channel getChannel() {
        return channel;
    }
}
  • NettyChannelInitializer.java
package cn.a.service.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.ssl.SslContext;
import org.springframework.context.ApplicationContext;

public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final ApplicationContext applicationContext;
    private final SslContext sslContext;

    public NettyChannelInitializer(ApplicationContext applicationContext, SslContext sslCtx) {
        this.applicationContext = applicationContext;
        this.sslContext = sslCtx;
    }

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        // 添加SSL安装验证
        channel.pipeline().addLast(sslContext.newHandler(channel.alloc()));
        //发送时编码
        channel.pipeline().addLast(new FrameEncoder());
        //接收时解码
        channel.pipeline().addLast(new FrameDecoder());
        //业务处理器
        channel.pipeline().addLast(new NettyMsgHandler(applicationContext));
    }
}

3.2. Client端

  • TestClientApp.java
package cn.a.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.a.service.netty.FrameDecoder;
import cn.a.service.netty.FrameEncoder;
import cn.a.service.netty.NettyMsg;
import cn.a.service.netty.Session;
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.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.File;
import java.util.Scanner;

@Slf4j
@SpringBootApplication
public class TestClientApp {

    private static final Session session = new Session().setId(IdUtil.randomUUID());

    public static void main(String[] args) {
        new Thread(new TestThread("127.0.0.1", 7890)).start();
    }

    private static class TestThread implements Runnable {
        private final String serverHost;
        private final int serverPort;

        public TestThread(String serverHost, int serverPort) {
            this.serverHost = serverHost;
            this.serverPort = serverPort;
        }

        @Override
        public void run() {
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                final String certsDir = "D:\\GIT\\secim_service\\service\\src\\main\\resources\\";
                File certChainFile = new File(certsDir + "client.crt");
                File keyFile = new File(certsDir + "pkcs8_client.key");
                File rootFile = new File(certsDir + "ca.crt");
                SslContext sslCtx = SslContextBuilder.forClient().keyManager(certChainFile, keyFile).trustManager(rootFile).build();

                Bootstrap b = new Bootstrap();

                b.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            protected void initChannel(SocketChannel ch) throws Exception {
                                // 添加SSL安装验证
                                ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
                                ch.pipeline().addLast(new FrameEncoder());
                                ch.pipeline().addLast(new FrameDecoder());
                                ch.pipeline().addLast(new TestClientHandler(session));
                            }
                        });

                // 发起异步连接操作
                ChannelFuture f = b.connect(serverHost, serverPort);
                f.addListener(future -> {
                    startConsoleThread(f.channel(), session);
                }).sync();
                // 等待客户端连接关闭
                f.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 优雅退出,释放NIO线程组
                group.shutdownGracefully();
            }
        }
    }

    /**
     * 开启控制台线程
     *
     * @param channel
     */
    private static void startConsoleThread(Channel channel, Session session) {
        new Thread(() -> {
            while (!Thread.interrupted()) {
                log.info("输入指令:");
                Scanner scanner = new Scanner(System.in);
                String input;
                while (!"exit".equals((input = scanner.nextLine()))) {
                    log.info("输入的命令是:{}", input);
                    if (!NumberUtil.isInteger(input)) {
                        log.error("输入的指令有误,请重新输入");
                        continue;
                    }
                    NettyMsg nettyMsg;
                    switch (Integer.parseInt(input)) {
                        case 1:
                            nettyMsg = TestMsgBuilder.buildIdentityMsg(session);
                            break;
                        default:
                            log.error("无法识别的指令:{},请重新输入指令", input);
                            nettyMsg = null;
                            break;
                    }
                    if (null != nettyMsg) {
                        channel.writeAndFlush(nettyMsg);
                    }
                }
            }
        }).start();
    }
}

3.3. 证书存放

在这里插入图片描述

4. 运行效果

4.1. SSL客户端发送消息:

在这里插入图片描述

4.2. 服务器收到SSL客户端消息:

在这里插入图片描述

4.3. 非SSL客户端发送消息:

在这里插入图片描述

4.4. 服务器收到非SSL客户端消息:

在这里插入图片描述

5. References:

2020-07-14 15:01:55 小傅哥:netty案例,netty4.1中级拓展篇十三《Netty基于SSL实现信息传输过程中双向加密验证》

2017-07-04 11:44 骏马金龙:openssl ca(签署和自建CA)

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一款高性能的网络应用框架,支持SSL/TLS加密来保护网络通信的安全性。SSL(Secure Sockets Layer)和TLS(Transport Layer Security)是网络通信中广泛使用的加密协议,用于在客户端和服务器之间建立安全的通信信道。 Netty提供了一些组件和类来实现SSL/TLS加密。首先,我们需要使用javax.net.ssl包中的类来创建SSLContext对象。SSLContext是SSL/TLS协议的入口点,它包含用于加密和解密数据的加密算法和密钥。我们需要为SSLContext对象配置密钥库和信任库,密钥库用于存储证书和私钥,而信任库用于存储可信的证书。 接下来,我们需要创建SslHandler对象,将其添加到Netty的ChannelPipeline中。SslHandler作为一个ChannelHandler,负责处理SSL/TLS握手过程和数据的加密解密。当建立连接时,SslHandler会自动执行握手过程,包括协商加密算法、验证证书以及生成会话密钥等。 一旦握手完成,SslHandler会将数据加密后发送到网络,并将接收到的密文解密成明文。这样可以确保在网络传输过程中的数据保密性和完整性。此外,SslHandler还提供了一些方法来获取会话信息,如远程主机的证书和协商的加密算法。 使用NettySSL/TLS加密功能能够有效地提高网络通信的安全性。通过配置SSLContext和添加SslHandler,我们可以方便地实现对网络通信的加密和解密。无论是在客户端还是服务器端,都可以使用NettySSL/TLS加密功能来保护数据的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值