有时RestApi接口需要实现双向认证,验证客户端请求的合法来源,这里用netty实现了https请求的双向认证
首先ide里生成一个maven项目,pom.xml加入netty依懒包
io.netty netty-all 4.1.20.Final
一、 openssl生成证书
在sslauth建立目录security存放证书,
注意生成证书common-name参数为localhost,用于本地测试,“A challenge password”可不输
1. 建立root CA
security目录下执行下列命令,建立要证书,用来做签名CA证书
2. 建立服务端证书
security建立service目录,存放服务端证书
3. 建立客户端证书
security建立client目录,存放客户端证书
二、 建立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{ 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
openssl pkcs12 -export -cacerts -inkey client/client.key -in client/client.crt -out client/client.p12
keytool -import -file ./rootCA.pem -keystore client/ca.jks
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测试脚本