netty 實現https服務器

0 概述

netty 通過JDK的SSLEngine,以SslHandler的方式提供對SSL/TLS 安全傳輸的支持,極大的簡化了開發工作。本文主要講述如何使用netty實現簡單的https服務器。

1 SSL單向認證

所謂的單向認證,即客戶端只驗證服務端的合法性,服務端不會驗證客戶端。
單向認證過程的總結如下:
1.SSL客戶端(瀏覽器)向服務端傳送客戶端 SSL協議的版本號、支持的加密算法種類、產生的隨機數,以及其他可選信息。
2.服務端返回握手應答,向客戶端傳送確認SSL協議的版本號、加密算法種類、隨機數以及其他信息。
3.服務端向客戶端發送自己的證書,這個證書其實就是公鑰,只是包含了很多信息,如證書的頒發機構,過期時間等等
4.客戶端對服務端的證書進行認證,服務端的合法性校驗包括:證書是否過期、發行服務器證書CA是否可靠、發行者證書的公鑰能否解開服務器證書的“發行者數字簽名”等
5.客戶端隨機產生一個用於後續通信的對稱密鑰,然後用服務端的公鑰加密傳輸給服務端,通知服務端客戶端的握手結束。
6.服務端解密獲取客戶端的對稱密鑰,同時通知客戶端服務端的握手結束。
7.SSL的握手部分結束,SSL安全通道建立,客戶端和服務端開始使用相同的對稱密鑰對數據進行加密,然後通過socket進行傳輸。

2 具體實現

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

利用JDK的keytool 工具,生成服務端私鑰和證書倉庫。
執行如下命令:
keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -dname “CN=localhost” -keypass hsc123 -storepass hsc123 -keystore local.jks

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

/**
 * Created by apple on 17/10/21.
 */
public class HttpsServer {

    public static void start(final int port) throws Exception {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        try {
            serverBootstrap.channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .group(boss, worker)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
                            sslEngine.setUseClientMode(false);
                            ch.pipeline().addLast(new SslHandler(sslEngine));
                            ch.pipeline().addLast("http-decoder", new HttpServerCodec());
                            ch.pipeline().addLast(new HttpsSeverHandler());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            future.channel().closeFuture().sync();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws Exception {
        start(7000);
    }

}
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;

/**
 * Created by apple on 17/10/21.
 */
public class SSLContextFactory {

    public static SSLContext getSslContext() throws Exception {
        char[] passArray = "hsc123".toCharArray();
        SSLContext sslContext = SSLContext.getInstance("TLSv1");
        KeyStore ks = KeyStore.getInstance("JKS");
        //加載keytool 生成的文件
        FileInputStream inputStream = new FileInputStream("/Users/apple/local.jks");
        ks.load(inputStream, passArray);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, passArray);
        sslContext.init(kmf.getKeyManagers(), null, null);
        inputStream.close();
        return sslContext;

    }
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;

/**
 * Created by apple on 17/10/21.
 */
public class HttpsSeverHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) msg;
            boolean keepaLive = HttpUtil.isKeepAlive(request);
            System.out.println("method" + request.method());
            System.out.println("uri" + request.uri());
            FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            httpResponse.content().writeBytes("https".getBytes());
            httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
            httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes());
            if (keepaLive) {
                httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                ctx.writeAndFlush(httpResponse);
            } else {
                ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
            }
        }
    }
}

3 測試

啓動服務 輸入https://127.0.0.1:7000

這裏寫圖片描述
參考文獻
[1] Netty 權威指南(第二版),李林峯著

發佈了199 篇原創文章 · 獲贊 111 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章