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主要可以避免以下几个安全问题:
- 窃听隐私:使用明文传输的HTTP协议,传输过程中的信息都可能会被攻击者窃取到,例如你登录网站的用户名和密码、在电商的购买记录、搜索记录等,这就会造成例如账号被盗、各种隐私泄漏的风险。而使用HTTPS对通信内容加密过后,即使被攻击者窃取到也无法破解其中的内容。
- 篡改内容:HTTP使用明文传输,不但消息会被窃取,还可能被篡改,例如常见的运营HTTP商劫持。你是否曾经浏览http协议的百度时,时不时会在页面下方弹出小广告,这些小广告并不是百度放上去的,而是电信网通等运营商干的,运营商通过篡改服务器返回的页面内容,加入一段HTML代码就可以轻松实现小广告。而使用HTTPS的百度,就不再会出现这样的小广告,因为攻击者无法对传输内容解密和加密,就无法篡改。

- 冒充:例如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服务器,在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一起学(十一):SSL/TLS
MINA、Netty、Twisted一起学(十二):HTTPS
源码
https://github.com/wucao/mina-netty-twisted
MINA、Netty、Twisted一起学(十二):HTTPS的更多相关文章
- Mina、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息
在TCP连接开始到结束连接,之间可能会多次传输数据,也就是服务器和客户端之间可能会在连接过程中互相传输多条消息.理想状况是一方每发送一条消息,另一方就立即接收到一条,也就是一次write对应一次rea ...
- 十二.HTTPS网站安全访问实践
期中集群架构-第十二章-HTTPS安全证书访问连接实践配置========================================= 01:网络安全涉及的问题: ①. 网络安全问题-数据机密性 ...
- Java从零开始学十二(构造方法)
一.构造方法 构造方法的主要目的是为类中的属性初始化 二.构造方法的定义格式 class 类名称{ 访问权限 类名称(类型1 参数1,类型2 参数2,…){ 程序语句 ; … // 构造方法没有 ...
- Mina、Netty、Twisted一起学(十):线程模型
要想开发一个高性能的TCP服务器,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,如果再搭配上高效率的代码,才能实现一个高大上的服务器.但是如果不了解它 ...
- Mina、Netty、Twisted一起学(八):HTTP服务器
HTTP协议应该是目前使用最多的应用层协议了,用浏览器打开一个网站就是使用HTTP协议进行数据传输. HTTP协议也是基于TCP协议,所以也有服务器和客户端.HTTP客户端一般是浏览器,当然还有可能是 ...
- Mina、Netty、Twisted一起学(九):异步IO和回调函数
用过JavaScript或者jQuery的同学都知道,JavaScript特别是jQuery中存在大量的回调函数,例如Ajax.jQuery的动画等. $.get(url, function() { ...
- Mina、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)
消息传递有很多种方式,请求/响应(Request/Reply)是最常用的.在前面的博文的例子中,很多都是采用请求/响应的方式,当服务器接收到消息后,会立即write回写一条消息到客户端.HTTP协议也 ...
- Mina、Netty、Twisted一起学(六):session
开发过Web应用的同学应该都会使用session.由于HTTP协议本身是无状态的,所以一个客户端多次访问这个web应用的多个页面,服务器无法判断多次访问的客户端是否是同一个客户端.有了session就 ...
- Mina、Netty、Twisted一起学(五):整合protobuf
protobuf是谷歌的Protocol Buffers的简称,用于结构化数据和字节码之间互相转换(序列化.反序列化),一般应用于网络传输,可支持多种编程语言. protobuf如何使用这里不再介绍, ...
随机推荐
- C语言-for循环
for循环是C语言中的循环语句之一,它的一般形式为for(初值,条件表达式,步长){语句};初值通常是一个赋值语句, 它用来给循环控制变量赋初值: 条件表达式是一个关系表达式, 它决定什么时候退出循环 ...
- iOS 参考 网络书籍
网络图书: Xcode中的Project和Target: http://book.51cto.com/art/201307/402283.htm
- [项目回顾]基于Annotation与SpringAOP的缓存简单解决方案
前言: 由于项目的原因,需要对项目中大量访问多修改少的数据进行缓存并管理,为达到开发过程中通过Annotation简单的配置既可以完成对缓存的设置与更新的需求,故而设计的该简易的解决方案. 涉及技术: ...
- iOS 之 CALayer与UIView的区别
最大区别:CALayer (图层)不会直接渲染到屏幕上. UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它.它本身完全是由CoreAnimation来实现的. 真正的绘图部分,是由一 ...
- 分布式环境中三种Session管理方法的使用场景及优缺点
在分布式环境,管理Session通常使用下面三种方式: 一.Session Replication 方式管理 (即session复制) 简介:将一台机器上的Session数据广播复制到 ...
- JSP EL表达式 获得 request的GET/POST方法
JSP EL表达式 获得 request的GET/POST方法: 不在requestScopse中: <p>得到request的方法</p> <p>pageCont ...
- lufylegend库 LTextField
lufylegend库 LTextField <!DOCTYPE html> <html lang="en"> <head> <meta ...
- 对angularjs时间过滤格式
在最近的项目中,我遇到这样的一个小问题,从后台传过来一个时间值,格式如下: "/Date(1484743002000)/" 至于为什么是这种格式,后台的开发说数据库中就是这个存的( ...
- Codeforces Round #257 (Div. 1)A~C(DIV.2-C~E)题解
今天老师(orz sansirowaltz)让我们做了很久之前的一场Codeforces Round #257 (Div. 1),这里给出A~C的题解,对应DIV2的C~E. A.Jzzhu and ...
- [JQuery]Jquery对象和dom对象
jquery对象是jquery包装dom对象后产生的对象,它们都只能使用各自的方法. 1.定义变量时,通过$来区分: var $variable = jquery对象: var variable = ...