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

相关文章
相关标签/搜索