由于HTTPS協(xié)議是由HTTP協(xié)議加上SSL/TLS協(xié)議組合而成,在閱讀本文前可以先閱讀一下HTTP服務(wù)器SSL/TLS兩篇博文,本文中的代碼也是由這兩篇博文中的代碼組合而成。

HTTPS介紹

上一篇博文中介紹了SSL/TLS協(xié)議,我們平時接觸最多的SSL/TLS協(xié)議的應(yīng)用就是HTTPS協(xié)議了,現(xiàn)在可以看到越來越多的網(wǎng)站已經(jīng)是https開頭了,百度搜索也由曾經(jīng)的http改為https。有關(guān)百度為什么升級https推薦閱讀:http://zhanzhang.baidu.com/wiki/383

HTTPS即HTTP over SSL,實際上就是在原來HTTP協(xié)議的底層加入了SSL/TLS協(xié)議層,使得客戶端(例如瀏覽器)與服務(wù)器之間的通信加密傳輸,攻擊者無法竊聽和篡改。相對而言HTTP協(xié)議則是明文傳輸,安全性并不高。

HTTPS主要可以避免以下幾個安全問題:

  1. 竊聽隱私:使用明文傳輸?shù)腍TTP協(xié)議,傳輸過程中的信息都可能會被攻擊者竊取到,例如你登錄網(wǎng)站的用戶名和密碼、在電商的購買記錄、搜索記錄等,這就會造成例如賬號被盜、各種隱私泄漏的風(fēng)險。而使用HTTPS對通信內(nèi)容加密過后,即使被攻擊者竊取到也無法破解其中的內(nèi)容。

  2. 篡改內(nèi)容:HTTP使用明文傳輸,不但消息會被竊取,還可能被篡改,例如常見的運(yùn)營HTTP商劫持。你是否曾經(jīng)瀏覽http協(xié)議的百度時,時不時會在頁面下方彈出小廣告,這些小廣告并不是百度放上去的,而是電信網(wǎng)通等運(yùn)營商干的,運(yùn)營商通過篡改服務(wù)器返回的頁面內(nèi)容,加入一段HTML代碼就可以輕松實現(xiàn)小廣告。而使用HTTPS的百度,就不再會出現(xiàn)這樣的小廣告,因為攻擊者無法對傳輸內(nèi)容解密和加密,就無法篡改。

  3. 冒充:例如DNS劫持,當(dāng)你輸入一個http網(wǎng)址在瀏覽器打開時,有可能打開的是一個假的網(wǎng)站,連的并不是真網(wǎng)站的服務(wù)器,假的網(wǎng)站可能給你彈出廣告,還可能讓你輸入用戶名密碼來盜取賬戶。使用HTTPS的話,服務(wù)器都會有數(shù)字證書和私鑰,數(shù)字證書公開的,私鑰是網(wǎng)站服務(wù)器私密的,假網(wǎng)站如果使用假的證書,瀏覽器會攔截并提示,如果使用真的證書,由于沒有私鑰也無法建立連接。

生成私鑰和證書

瀏覽器信任的證書一般是CA機(jī)構(gòu)(證書授權(quán)中心)頒發(fā)的,證書有收費(fèi)的也有免費(fèi)的,本文使用免費(fèi)證書用于測試??梢栽隍v訊云https://www.qcloud.com/product/ssl申請一個免費(fèi)證書,申請證書前需要提供一個域名,即該證書作用的域名。

我在本文中使用的是我自己的域名gw2.vsgames.cn在騰訊云申請的免費(fèi)證書,如果沒有自己的域名無法申請免費(fèi)證書,可以在本文的末尾下載源碼,其中有我生成好的證書用于測試。

證書生成好下載后包含一個私鑰文件(.key)和一個證書文件(.crt),騰訊云生成的證書可以在Nginx目錄下找到這兩個文件。

這兩個文件在Twisted中可以直接使用,但是Java只能使用PKCS#8私鑰文件,需要對上面的.key文件用openssl進(jìn)行轉(zhuǎn)換(如果你是在我提供的源碼中獲取證書和私鑰文件,我已經(jīng)提供了轉(zhuǎn)換好的私鑰,可以跳過這一步)。

轉(zhuǎn)換成DER二進(jìn)制格式私鑰文件,供MINA使用:

openssl pkcs8 -topk8 -inform PEM -in 2_gw2.vsgames.cn.key -outform DER -nocrypt -out private.der

轉(zhuǎn)換成PEM文本格式私鑰文件,供Netty使用:

openssl pkcs8 -topk8 -inform PEM -in 2_gw2.vsgames.cn.key -outform PEM -nocrypt -out private.pem

除了在CA機(jī)構(gòu)申請證書,還可以通過自簽名的方式生成私鑰和證書,上一篇博文中采用的就是這種方式。不過由于自簽名的證書不是CA機(jī)構(gòu)頒發(fā),不受瀏覽器信任,在瀏覽器打開HTTPS地址時會有安全提示,測試時可以忽略提示。

HTTPS服務(wù)器實現(xiàn)

MINA、Netty、Twisted一起學(xué)(八):HTTP服務(wù)器MINA、Netty、Twisted一起學(xué)(十一):SSL/TLS中的代碼結(jié)合起來,即可實現(xiàn)HTTPS服務(wù)器。

MINA

http://xxgblog.com/2014/09/23/mina-netty-twisted-8/#MINA代碼的基礎(chǔ)上,在HttpServerCodec之前加上SslFilter即可。

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

public class MinaServer {    public static void main(String[] args) throws Exception {

        String certPath = "/Users/wucao/Desktop/https/1_gw2.vsgames.cn_bundle.crt";  // 證書
        String privateKeyPath = "/Users/wucao/Desktop/https/private.der";  // 私鑰        // 證書        // https://docs.oracle.com/javase/7/docs/api/java/security/cert/X509Certificate.html
        InputStream inStream = null;
        Certificate certificate = null;        try {
            inStream = new FileInputStream(certPath);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            certificate = cf.generateCertificate(inStream);
        } finally {            if (inStream != null) {
                inStream.close();
            }
        }        // 私鑰
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Files.readAllBytes(new File(privateKeyPath).toPath()));
        PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null, null);
        Certificate[] certificates = {certificate};
        ks.setKeyEntry("key", privateKey, "".toCharArray(), certificates);

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, "".toCharArray());

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, null);



        IoAcceptor acceptor = new NioSocketAcceptor();
        DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
        chain.addLast("ssl", new SslFilter(sslContext));  // SslFilter + HttpServerCodec實現(xiàn)HTTPS
        chain.addLast("codec", new HttpServerCodec());
        acceptor.setHandler(new HttpServerHandle());
        acceptor.bind(new InetSocketAddress(8080));
    }
}class HttpServerHandle extends IoHandlerAdapter {

    @Override    public void exceptionCaught(IoSession session, Throwable cause)            throws Exception {
        cause.printStackTrace();
    }

    @Override    public void messageReceived(IoSession session, Object message)            throws Exception {        if (message instanceof HttpRequest) {            // 請求,解碼器將請求轉(zhuǎn)換成HttpRequest對象
            HttpRequest request = (HttpRequest) message;            // 獲取請求參數(shù)
            String name = request.getParameter("name");            if(name == null) {
                name = "World";
            }
            name = URLDecoder.decode(name, "UTF-8");            // 響應(yīng)HTML
            String responseHtml = "<html><body>Hello, " + name + "</body></html>";            byte[] responseBytes = responseHtml.getBytes("UTF-8");            int contentLength = responseBytes.length;            // 構(gòu)造HttpResponse對象,HttpResponse只包含響應(yīng)的status line和header部分
            Map<String, String> headers = new HashMap<String, String>();
            headers.put("Content-Type", "text/html; charset=utf-8");
            headers.put("Content-Length", Integer.toString(contentLength));
            HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SUCCESS_OK, headers);            // 響應(yīng)BODY
            IoBuffer responseIoBuffer = IoBuffer.allocate(contentLength);
            responseIoBuffer.put(responseBytes);
            responseIoBuffer.flip();

            session.write(response); // 響應(yīng)的status line和header部分
            session.write(responseIoBuffer); // 響應(yīng)body部分        }
    }
}

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

Netty 

http://xxgblog.com/2014/09/23/mina-netty-twisted-8/#Netty代碼的基礎(chǔ)上,在ChannelPipeline最前邊加上SslHandler即可。

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

public class NettyServer {    public static void main(String[] args) throws InterruptedException, SSLException {

        File certificate = new File("/Users/wucao/Desktop/https/1_gw2.vsgames.cn_bundle.crt");  // 證書
        File privateKey = new File("/Users/wucao/Desktop/https/private.pem");  // 私鑰
        final SslContext sslContext = SslContextBuilder.forServer(certificate, privateKey).build();

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();                            // 加入SslHandler實現(xiàn)HTTPS
                            SslHandler sslHandler = sslContext.newHandler(ch.alloc());
                            pipeline.addLast(sslHandler);

                            pipeline.addLast(new HttpServerCodec());
                            pipeline.addLast(new HttpServerHandler());
                        }
                    });
            ChannelFuture f = b.bind(8080).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}class HttpServerHandler extends ChannelInboundHandlerAdapter {

    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {        if (msg instanceof HttpRequest) {            // 請求,解碼器將請求轉(zhuǎn)換成HttpRequest對象
            HttpRequest request = (HttpRequest) msg;            // 獲取請求參數(shù)
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
            String name = "World";            if(queryStringDecoder.parameters().get("name") != null) {
                name = queryStringDecoder.parameters().get("name").get(0);
            }            // 響應(yīng)HTML
            String responseHtml = "<html><body>Hello, " + name + "</body></html>";            byte[] responseBytes = responseHtml.getBytes("UTF-8");            int contentLength = responseBytes.length;            // 構(gòu)造FullHttpResponse對象,F(xiàn)ullHttpResponse包含message body
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(responseBytes));
            response.headers().set("Content-Type", "text/html; charset=utf-8");
            response.headers().set("Content-Length", Integer.toString(contentLength));

            ctx.writeAndFlush(response);
        }
    }

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

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

Twisted 

http://xxgblog.com/2014/09/23/mina-netty-twisted-8/#Twisted中reactor.listenTCP改為的reactor.listenSSL,即可從HTTP協(xié)議切到HTTPS協(xié)議。

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

# -*- coding:utf-8 –*-from twisted.internet import reactor, sslfrom twisted.web import server, resource

sslContext = ssl.DefaultOpenSSLContextFactory(    '/Users/wucao/Desktop/https/2_gw2.vsgames.cn.key',  # 私鑰
    '/Users/wucao/Desktop/https/1_gw2.vsgames.cn_bundle.crt',  # 證書)class MainResource(resource.Resource):

    isLeaf = True    # 用于處理GET類型請求
    def render_GET(self, request):        # name參數(shù)
        name = 'World'
        if request.args.has_key('name'):
            name = request.args['name'][0]        # 設(shè)置響應(yīng)編碼
        request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")        # 響應(yīng)的內(nèi)容直接返回
        return "<html><body>Hello, " + name + "</body></html>"site = server.Site(MainResource())
reactor.listenSSL(8080, site, sslContext)
reactor.run()

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

客戶端測試 

由于瀏覽器就是最天然的HTTPS客戶端,這里可以使用瀏覽器來測試。

首先,由于我的證書對應(yīng)的域名是gw2.vsgames.cn,而服務(wù)器代碼運(yùn)行在本機(jī)上,所以先需要配置hosts將域名解析到localhost上:

127.0.0.1 gw2.vsgames.cn

在瀏覽器打開https://gw2.vsgames.cn:8080/?name=叉叉哥可以看到測試結(jié)果: