Netty、MINA、Twisted一塊兒學(十二):HTTPS

文章已得到做者受權,原文地址:
xxgblog.com/2017/02/28/mina-netty-twisted-12html

1. HTTPS介紹

上一篇博文中介紹了SSL/TLS協議,咱們平時接觸最多的SSL/TLS協議的應用就是HTTPS協議了,如今能夠看到愈來愈多的網站已是https開頭了,百度搜索也由曾經的http改成https。有關百度爲何升級https推薦閱讀:http://zhanzhang.baidu.com/wiki/383java

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

HTTPS主要能夠避免如下幾個安全問題:web

1)竊聽隱私:使用明文傳輸的HTTP協議,傳輸過程當中的信息均可能會被***者竊取到,例如你登陸網站的用戶名和密碼、在電商的購買記錄、搜索記錄等,這就會形成例如帳號被盜、各類隱私泄漏的風險。而使用HTTPS對通訊內容加密事後,即便被***者竊取到也沒法破解其中的內容。api

篡改內容:HTTP使用明文傳輸,不但消息會被竊取,還可能被篡改,例如常見的運營HTTP商劫持。你是否曾經瀏覽http協議的百度時,時不時會在頁面下方彈出小廣告,這些小廣告並非百度放上去的,而是電信網通等運營商乾的,運營商經過篡改服務器返回的頁面內容,加入一段HTML代碼就能夠輕鬆實現小廣告。而使用HTTPS的百度,就再也不會出現這樣的小廣告,由於***者沒法對傳輸內容解密和加密,就沒法篡改。
Netty、MINA、Twisted一塊兒學(十二):HTTPS瀏覽器

3)冒充:例如DNS劫持,當你輸入一個http網址在瀏覽器打開時,有可能打開的是一個假的網站,連的並非真網站的服務器,假的網站可能給你彈出廣告,還可能讓你輸入用戶名密碼來盜取帳戶。使用HTTPS的話,服務器都會有數字證書和私鑰,數字證書公開的,私鑰是網站服務器私密的,假網站若是使用假的證書,瀏覽器會攔截並提示,若是使用真的證書,因爲沒有私鑰也沒法創建鏈接。
Netty、MINA、Twisted一塊兒學(十二):HTTPS安全

2. 生成私鑰和證書

瀏覽器信任的證書通常是CA機構(證書受權中心)頒發的,證書有收費的也有免費的,本文使用免費證書用於測試。能夠在騰訊雲https://www.qcloud.com/product/ssl申請一個免費證書,申請證書前須要提供一個域名,即該證書做用的域名。服務器

我在本文中使用的是我本身的域名gw2.vsgames.cn在騰訊雲申請的免費證書,若是沒有本身的域名沒法申請免費證書,能夠在本文的末尾下載源碼,其中有我生成好的證書用於測試。markdown

證書生成好下載後包含一個私鑰文件(.key)和一個證書文件(.crt),騰訊雲生成的證書能夠在Nginx目錄下找到這兩個文件。session

這兩個文件在Twisted中能夠直接使用,可是Java只能使用PKCS#8私鑰文件,須要對上面的.key文件用openssl進行轉換(若是你是在我提供的源碼中獲取證書和私鑰文件,我已經提供了轉換好的私鑰,能夠跳過這一步)。

轉換成DER二進制格式私鑰文件,供MINA使用:

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

轉換成PEM文本格式私鑰文件,供Netty使用:

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

除了在CA機構申請證書,還能夠經過自簽名的方式生成私鑰和證書,上一篇博文中採用的就是這種方式。不過因爲自簽名的證書不是CA機構頒發,不受瀏覽器信任,在瀏覽器打開HTTPS地址時會有安全提示,測試時能夠忽略提示。

3. HTTPS服務器實現

將MINA、Netty、Twisted一塊兒學(八):HTTP服務器和MINA、Netty、Twisted一塊兒學(十一):SSL/TLS中的代碼結合起來,便可實現HTTPS服務器。

MINA

在http://xxgblog.com/2014/09/23/mina-netty-twisted-8/#MINA代碼的基礎上,在HttpServerCodec以前加上SslFilter便可。

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實現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) {

           // 請求,解碼器將請求轉換成HttpRequest對象
           HttpRequest request = (HttpRequest) message;

           // 獲取請求參數
           String name = request.getParameter("name");
           if(name == null) {
               name = "World";
           }
           name = URLDecoder.decode(name, "UTF-8");

           // 響應HTML
           String responseHtml = "<html><body>Hello, " + name + "</body></html>";
           byte[] responseBytes = responseHtml.getBytes("UTF-8");
           int contentLength = responseBytes.length;

           // 構造HttpResponse對象,HttpResponse只包含響應的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);

           // 響應BODY
           IoBuffer responseIoBuffer = IoBuffer.allocate(contentLength);
           responseIoBuffer.put(responseBytes);
           responseIoBuffer.flip();

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

Netty

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

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實現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) {

           // 請求,解碼器將請求轉換成HttpRequest對象
           HttpRequest request = (HttpRequest) msg;

           // 獲取請求參數
           QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
           String name = "World";
           if(queryStringDecoder.parameters().get("name") != null) {
               name = queryStringDecoder.parameters().get("name").get(0);
           }

           // 響應HTML
           String responseHtml = "<html><body>Hello, " + name + "</body></html>";
           byte[] responseBytes = responseHtml.getBytes("UTF-8");
           int contentLength = responseBytes.length;

           // 構造FullHttpResponse對象,FullHttpResponse包含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();
   }
}

Twisted

將http://xxgblog.com/2014/09/23/mina-netty-twisted-8/#Twisted中reactor.listenTCP改成的reactor.listenSSL,便可從HTTP協議切到HTTPS協議。

# -*- coding:utf-8 –*-

from twisted.internet import reactor, ssl
from 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參數
       name = 'World'
       if request.args.has_key('name'):
           name = request.args['name'][0]

       # 設置響應編碼
       request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")

       # 響應的內容直接返回
       return "<html><body>Hello, " + name + "</body></html>"

site = server.Site(MainResource())
reactor.listenSSL(8080, site, sslContext)
reactor.run()

4. 客戶端測試

因爲瀏覽器就是最自然的HTTPS客戶端,這裏可使用瀏覽器來測試。

首先,因爲個人證書對應的域名是gw2.vsgames.cn,而服務器代碼運行在本機上,因此先須要配置hosts將域名解析到localhost上:

127.0.0.1 gw2.vsgames.cn

在瀏覽器打開https://gw2.vsgames.cn:8080/?name=叉叉哥能夠看到測試結果:
Netty、MINA、Twisted一塊兒學(十二):HTTPS

證書和私鑰正確的HTTPS服務器,在Chrome瀏覽器左上角會有「安全」提示,其餘瀏覽器也會有相應的提示。

END

往期精彩

01 Netty、MINA、Twisted一塊兒學系列01:實現簡單的TCP服務器
02 Netty、MINA、Twisted一塊兒學系列02:TCP消息邊界問題及按行分割消息
03 Netty、MINA、Twisted一塊兒學系列03:TCP消息固定大小的前綴(Header)
04 Netty、MINA、Twisted一塊兒學系列04:定製本身的協議
05 Netty、MINA、Twisted一塊兒學系列05:整合protobuf
06 Netty、MINA、Twisted一塊兒學系列06:session
07 Netty、MINA、Twisted一塊兒學系列07:發佈/訂閱(Publish/Subscribe)
08 Netty、MINA、Twisted一塊兒學系列08:HTTP服務器
09 Netty、MINA、Twisted一塊兒學系列09:異步IO和回調函數
10 Netty、MINA、Twisted一塊兒學系列10:線程模型
11 Netty、MINA、Twisted一塊兒學系列10:SSL / TLS

關注我
天天進步一點點
Netty、MINA、Twisted一塊兒學(十二):HTTPS

相關文章
相關標籤/搜索