zoukankan      html  css  js  c++  java
  • MINA、Netty、Twisted一起学(十二):HTTPS

    由于HTTPS协议是由HTTP协议加上SSL/TLS协议组合而成,在阅读本文前可以先阅读一下HTTP服务器SSL/TLS两篇博文,本文中的代码也是由这两篇博文中的代码组合而成。

    HTTPS介绍

    上一篇博文中介绍了SSL/TLS协议,我们平时接触最多的SSL/TLS协议的应用就是HTTPS协议了,现在可以看到越来越多的网站已经是https开头了,百度搜索也由曾经的http改为https。有关百度为什么升级https推荐阅读:http://zhanzhang.baidu.com/wiki/383

    HTTPS即HTTP over SSL,实际上就是在原来HTTP协议的底层加入了SSL/TLS协议层,使得客户端(例如浏览器)与服务器之间的通信加密传输,攻击者无法窃听和篡改。相对而言HTTP协议则是明文传输,安全性并不高。

    HTTPS主要可以避免以下几个安全问题:

    1. 窃听隐私:使用明文传输的HTTP协议,传输过程中的信息都可能会被攻击者窃取到,例如你登录网站的用户名和密码、在电商的购买记录、搜索记录等,这就会造成例如账号被盗、各种隐私泄漏的风险。而使用HTTPS对通信内容加密过后,即使被攻击者窃取到也无法破解其中的内容。
    2. 篡改内容:HTTP使用明文传输,不但消息会被窃取,还可能被篡改,例如常见的运营HTTP商劫持。你是否曾经浏览http协议的百度时,时不时会在页面下方弹出小广告,这些小广告并不是百度放上去的,而是电信网通等运营商干的,运营商通过篡改服务器返回的页面内容,加入一段HTML代码就可以轻松实现小广告。而使用HTTPS的百度,就不再会出现这样的小广告,因为攻击者无法对传输内容解密和加密,就无法篡改。
      HTTP劫持插入广告
    3. 冒充:例如DNS劫持,当你输入一个http网址在浏览器打开时,有可能打开的是一个假的网站,连的并不是真网站的服务器,假的网站可能给你弹出广告,还可能让你输入用户名密码来盗取账户。使用HTTPS的话,服务器都会有数字证书和私钥,数字证书公开的,私钥是网站服务器私密的,假网站如果使用假的证书,浏览器会拦截并提示,如果使用真的证书,由于没有私钥也无法建立连接。
      证书有问题时的浏览器提示

    生成私钥和证书

    浏览器信任的证书一般是CA机构(证书授权中心)颁发的,证书有收费的也有免费的,本文使用免费证书用于测试。可以在腾讯云https://www.qcloud.com/product/ssl申请一个免费证书,申请证书前需要提供一个域名,即该证书作用的域名。

    我在本文中使用的是我自己的域名gw2.vsgames.cn在腾讯云申请的免费证书,如果没有自己的域名无法申请免费证书,可以在本文的末尾下载源码,其中有我生成好的证书用于测试。

    证书生成好下载后包含一个私钥文件(.key)和一个证书文件(.crt),腾讯云生成的证书可以在Nginx目录下找到这两个文件。

    这两个文件在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地址时会有安全提示,测试时可以忽略提示。

    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()

    客户端测试 

    由于浏览器就是最天然的HTTPS客户端,这里可以使用浏览器来测试。

    首先,由于我的证书对应的域名是gw2.vsgames.cn,而服务器代码运行在本机上,所以先需要配置hosts将域名解析到localhost上:

    127.0.0.1 gw2.vsgames.cn

    在浏览器打开https://gw2.vsgames.cn:8080/?name=叉叉哥可以看到测试结果: 

    浏览器测试HTTPS服务器
    证书和私钥正确的HTTPS服务器,在Chrome浏览器左上角会有“安全”提示,其他浏览器也会有相应的提示。

    MINA、Netty、Twisted一起学系列

    MINA、Netty、Twisted一起学(一):实现简单的TCP服务器

    MINA、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息

    MINA、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)

    MINA、Netty、Twisted一起学(四):定制自己的协议

    MINA、Netty、Twisted一起学(五):整合protobuf

    MINA、Netty、Twisted一起学(六):session

    MINA、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

    MINA、Netty、Twisted一起学(八):HTTP服务器

    MINA、Netty、Twisted一起学(九):异步IO和回调函数

    MINA、Netty、Twisted一起学(十):线程模型

    MINA、Netty、Twisted一起学(十一):SSL/TLS

    MINA、Netty、Twisted一起学(十二):HTTPS

    源码

    https://github.com/wucao/mina-netty-twisted

  • 相关阅读:
    PHP防止跨站表单提交与同站跨页伪造表单的攻击
    dz数据结构
    DiscuzX的目录权限设置1
    discuz 文档说明
    discuz x 系列目录结构说明
    验证码问题
    Discuz! X2验证码的产生及验证
    Discuz 升级X3问题汇总整理
    IIS7以上版本去掉伪静态去掉index.php方法
    Discuz 模板标签说明
  • 原文地址:https://www.cnblogs.com/wucao/p/6478336.html
Copyright © 2011-2022 走看看