netty實現SSL 雙向認證與用jmeter測試

 有時RestApi接口需要實現雙向認證,驗證客戶端請求的合法來源,這裏用netty實現了https請求的雙向認證

首先ide裏生成一個maven項目,pom.xml加入netty依懶包

 <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.20.Final</version>
</dependency>

 一、   openssl生成證書

  sslauth建立目錄security存放證書,

注意生成證書common-name參數爲localhost,用於本地測試,“A challenge password”可不輸

 1. 建立root CA

  security目錄下執行下列命令,建立要證書,用來做簽名CA證書

  • openssl genrsa -out rootCA.key 2048
  •  openssl req -x509 -new -nodes -key rootCA.key -days   1024 -out rootCA.pem

 2. 建立服務端證書

  security建立service目錄,存放服務端證書

  • openssl genrsa -out service/service.key 2048
  • openssl req -new -key service/service.key -out service/service.csr
  • openssl x509 -req -in service/service.csr -CA ./rootCA.pem -CAkey ./rootCA.key -CAcreateserial -out service/service.crt -days 500 -sha256
  • openssl pkcs8 -topk8 -inform PEM -outform PEM -in service/service.key -out service/service.pkcs8.key -nocrypt

3. 建立客戶端證書

   security建立client目錄,存放客戶端證書

  • openssl genrsa -out client/client.key 2048
  • openssl req -new -key client/client.key -out client/client.csr
  • openssl x509 -req -in client/client.csr -CA ./rootCA.pem -CAkey ./rootCA.key -CAcreateserial -out client/client.crt -days 500 -sha256
  • openssl pkcs8 -topk8 -inform PEM -outform PEM -in client/client.key -out client/client.pkcs8.key -nocrypt

二、   建立netty https

 1. 建立DefaultRequestHandler

  用來處理用戶請求,輸出HelloWorld

public class DefaultRequestHandler extends ChannelInboundHandlerAdapter {
    private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };

    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");
    private static final AsciiString CONNECTION = AsciiString.cached("Connection");
    private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof HttpRequest) {
            HttpRequest req = (HttpRequest) msg;

            boolean keepAlive = HttpUtil.isKeepAlive(req);
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
            response.headers().set(CONTENT_TYPE, "text/plain");
            response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());

            if (!keepAlive) {
                ctx.write(response).addListener(ChannelFutureListener.CLOSE);
            } else {
                response.headers().set(CONNECTION, KEEP_ALIVE);
                ctx.write(response);
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

 

2. 建立PipelineInitializer

 初始化netty pipelline,加入ssl handler

public class PipelineInitializer extends ChannelInitializer<SocketChannel> {
    private final SslContext sslCtx;

    public PipelineInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }
        pipeline.addLast("decoder", new HttpRequestDecoder());
        pipeline.addLast("encoder", new HttpResponseEncoder());
        pipeline.addLast("aggregator", new HttpObjectAggregator(20480));
        pipeline.addLast("chunkWriter", new ChunkedWriteHandler());
        pipeline.addLast(new DefaultRequestHandler());
    }
}

 

3. 建立Server

       應用主類,啓動應用,接收4個參數:應用的端口、服務端證書(service/ service.crt)、服務端私鑰(service/ service.pkcs8.key)、根證書(rootCA.pem)

         public final class Server {
    static int PORT = 8443;
    public static void main(String[] args) throws Exception {
        if (args.length < 4) {
            System.err.println("args=: port cert privateKey caKey");
            System.exit(1);
        }
        final SslContext sslCtx;
        PORT = Integer.parseInt(args[0]);
        File cert = new File(args[1]);
        File priKey = new File(args[2]);
        File caKey = new File(args[3]);
        sslCtx = SslContextBuilder.forServer(cert, priKey)
                .clientAuth(ClientAuth.REQUIRE)
                .trustManager(caKey).build();
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.option(ChannelOption.SO_BACKLOG, 1024);
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new PipelineInitializer(sslCtx));
            Channel ch = b.bind(PORT).sync().channel();
            System.err.println("https://127.0.0.1:" + PORT + '/');
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

 

三、   jmeter測試

1. 啓動jmeter

  • 導出客戶端證書爲p12格式,導出密碼:123456

      openssl pkcs12 -export -cacerts -inkey client/client.key -in client/client.crt -out client/client.p12

  • keytool導入根證書,密碼:123456

      keytool -import -file ./rootCA.pem -keystore client/ca.jks

  •  啓動jmeter

 bin/jmeter -Djavax.net.ssl.trustStore=security/ca.store -Djavax.net.ssl.keyStorePassword=123456 -Djavax.net.ssl.keyStore=security/client/client.p12

2. jmeter測試腳本

新建測試計劃=》建立線程組=》建立HTTP請求(路徑:https://localhost:8443)=》察看結果樹

3. 啓動jmeter測試腳本


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