因爲HTTPS協議是由HTTP協議加上SSL/TLS協議組合而成,在閱讀本文前能夠先閱讀一下HTTP服務器和SSL/TLS兩篇博文,本文中的代碼也是由這兩篇博文中的代碼組合而成。html
上一篇博文中介紹了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主要能夠避免如下幾個安全問題:git
瀏覽器信任的證書通常是CA機構(證書受權中心)頒發的,證書有收費的也有免費的,本文使用免費證書用於測試。能夠在騰訊雲https://www.qcloud.com/product/ssl申請一個免費證書,申請證書前須要提供一個域名,即該證書做用的域名。github
我在本文中使用的是我本身的域名gw2.vsgames.cn在騰訊雲申請的免費證書,若是沒有本身的域名沒法申請免費證書,能夠在本文的末尾下載源碼,其中有我生成好的證書用於測試。web
證書生成好下載後包含一個私鑰文件(.key)和一個證書文件(.crt),騰訊雲生成的證書能夠在Nginx目錄下找到這兩個文件。api
這兩個文件在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地址時會有安全提示,測試時能夠忽略提示。
將MINA、Netty、Twisted一塊兒學(八):HTTP服務器和MINA、Netty、Twisted一塊兒學(十一):SSL/TLS中的代碼結合起來,便可實現HTTPS服務器。
在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=叉叉哥能夠看到測試結果:
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