有時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測試腳本