由于不同环境过于复杂,本文仅基于Mac OS和Linux来讲解工具及应用。
目录结构:
本文需要安装的软件为(Mac用户请使用homebrew来安装,括号里都是Mac的安装方式)
wget https://curl.haxx.se/download/curl-7.48.0.tar.gz
tar xvf curl-7.48.0.tar.gz && cd curl-7.48.0
$codename
自行去该网站查询,比如Ubuntu 14.04 是 trusty;
deb http://nginx.org/packages/mainline/ubuntu/ $codename nginx deb-src http://nginx.org/packages/mainline/ubuntu/ $codename nginx/etc/apt/sources.list
sudo apt-get update && sudo apt-get install nginx
就可以装最新支持HTTP2的nginx啦;打开提供的Demo文件,terminal打开/keys
路径,输入./ca.sh http2test.com
,也可以生成其他域名或泛域名(*.xx.com)。注意:也可以不用生成证书,直接使用keys文件下提供的http2test.com证书;
打开Let’s Encrypt的官网,这里讲解如果已经启动nginx的情况下,如何签发证书。
步骤:
git clone https://github.com/letsencrypt/letsencrypt && cd letsencrypt
./letsencrypt-auto --help
会执行一些初始化工作,并且显示支持的命令;~/www
下,此步骤需要已搭建好服务器,并且可以通过example.com访问~/www
里的内容,如果是动态网站,需要在nginx层设置一个映射,将/.well-known/acme-challenge
映射到刚设置好的目录,也就是~/www
。
location /.well-known/acme-challenge { root /home/$username/www; }./letsencrypt-auto certonly --webroot -w ~/www -d example.com
,example.com是你希望申请证书的域名,然后证书下发成功。sudo nginx -t && sudo nginx -s reload
就可以看到啦curl的基本用法是:
curl -v -o /dev/null --http2 http://nghttp2.org
这里输入的是HTTP而不是HTTPS是因为,这里会采用HTTP2的ClearText模式,使用101 改变协议
协商升级为HTTP2协议。
Connected to nghttp2.org (106.186.112.116) port 80 (#0)> GET / HTTP/1.1> Host: nghttp2.org
> User-Agent: curl/7.48.0> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__>
< HTTP/1.1 101 Switching Protocols
< Connection: Upgrade
< Upgrade: h2c
* Received 101* Using HTTP2, server supports multi-use* Connection state changed (HTTP/2 confirmed)
* TCP_NODELAY set
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=21* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2.0 200< date:Sun, 10 Apr 2016 16:52:43 GMT
< content-type:text/html
< content-length:6646< last-modified:Sun, 03 Apr 2016 06:53:14 GMT
< etag:"5700bdda-19f6"< link:</stylesheets/screen.css>; rel=preload; as=stylesheet
< accept-ranges:bytes
< x-backend-header-rtt:0.000625< server:nghttpx nghttp2/1.10.0-DEV
< via:2 nghttpx
< x-frame-options:SAMEORIGIN
< x-xss-protection:1; mode=block
< x-content-type-options:nosniff
<
现在的大多数网站,都是通过302跳转到HTTPS网站来协商升级的,例如我厂的QQ邮箱;
Connected to mail.qq.com (183.60.15.162) port 80 (#0)> GET / HTTP/1.1> Host: mail.qq.com> User-Agent: curl/7.48.0> Accept: */*> Connection: Upgrade, HTTP2-Settings> Upgrade: h2c> HTTP2-Settings: AAMAAABkAAQAAP__>
< HTTP/1.1 302 Found< Server: TWS< Connection: close< Date: Sun, 10 Apr 2016 16:58:48 GMT< Content-Type: text/html; charset=GB18030< Location: https://mail.qq.com/cgi-bin/loginpage< Content-Length: 0<
官方文档,安装nghttp2后有配套的nghttp 客户端
、nghttpd 服务器
、nghttpx 反向代理
、h2load 负载测试
等工具。
输入命令nghttp -nv https://nghttp2.org
(n代表不输出,v代表详细信息):
结果列出了连接过程中的HTTP2各个Stream信息,例如SETTINGS Frame,HEADER Frame等,也可以带上参数:
[ 0.170] Connected
The negotiated protocol: h2
[ 0.732] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.732] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.732] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.732] send HEADERS frame <length=36, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /
:scheme: https
:authority: nghttp2.org
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.9.1
[ 0.891] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.891] recv (stream_id=13) :method: GET
[ 0.891] recv (stream_id=13) :scheme: https
[ 0.892] recv (stream_id=13) :path: /stylesheets/screen.css
[ 0.892] recv (stream_id=13) :authority: nghttp2.org
[ 0.892] recv (stream_id=13) accept-encoding: gzip, deflate
[ 0.892] recv (stream_id=13) user-agent: nghttp2/1.9.1
[ 0.892] recv PUSH_PROMISE frame <length=47, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.892] recv (stream_id=13) :status: 200
[ 0.892] recv (stream_id=13) date: Sun, 10 Apr 2016 17:00:25 GMT
[ 0.892] recv (stream_id=13) content-type: text/html
[ 0.892] recv (stream_id=13) content-length: 6646
[ 0.892] recv (stream_id=13) last-modified: Sun, 03 Apr 2016 06:53:14 GMT
[ 0.892] recv (stream_id=13) etag: "5700bdda-19f6"
[ 0.892] recv (stream_id=13) link: </stylesheets/screen.css>; rel=preload; as=stylesheet
[ 0.892] recv (stream_id=13) accept-ranges: bytes
[ 0.892] recv (stream_id=13) x-backend-header-rtt: 0.000663
[ 0.892] recv (stream_id=13) strict-transport-security: max-age=31536000
[ 0.892] recv (stream_id=13) server: nghttpx nghttp2/1.10.0-DEV
[ 0.892] recv (stream_id=13) via: 2 nghttpx
[ 0.892] recv (stream_id=13) x-frame-options: SAMEORIGIN
[ 0.892] recv (stream_id=13) x-xss-protection: 1; mode=block
[ 0.892] recv (stream_id=13) x-content-type-options: nosniff
[ 0.892] recv HEADERS frame <length=266, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 0.893] recv DATA frame <length=6646, flags=0x01, stream_id=13>
; END_STREAM
[ 0.893] recv (stream_id=2) :status: 200
[ 0.893] recv (stream_id=2) date: Sun, 10 Apr 2016 17:00:25 GMT
[ 0.893] recv (stream_id=2) content-type: text/css
[ 0.893] recv (stream_id=2) content-length: 39082
[ 0.893] recv (stream_id=2) last-modified: Sun, 03 Apr 2016 06:53:14 GMT
[ 0.893] recv (stream_id=2) etag: "5700bdda-98aa"
[ 0.893] recv (stream_id=2) accept-ranges: bytes
[ 0.893] recv (stream_id=2) x-backend-header-rtt: 0.000427
[ 0.893] recv (stream_id=2) strict-transport-security: max-age=31536000
[ 0.893] recv (stream_id=2) server: nghttpx nghttp2/1.10.0-DEV
[ 0.893] recv (stream_id=2) via: 2 nghttpx
[ 0.893] recv (stream_id=2) x-frame-options: SAMEORIGIN
[ 0.893] recv (stream_id=2) x-xss-protection: 1; mode=block
[ 0.893] recv (stream_id=2) x-content-type-options: nosniff
[ 0.893] recv (stream_id=2) x-http2-push: 1
[ 0.893] recv HEADERS frame <length=62, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.407] recv DATA frame <length=16384, flags=0x00, stream_id=2>[ 1.563] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33412)
[ 1.740] recv DATA frame <length=16384, flags=0x00, stream_id=2>[ 1.741] recv DATA frame <length=6314, flags=0x01, stream_id=2>
; END_STREAM
[ 1.741] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
还有这些参数:
-v : 显示详细信息-n : 不输出请求返回数据内容-t : 请求超时设置-w : 设置初始窗口大小-a : 解析页面获取页面上的资源-H : 给请求增加特定头部信息,例如 -H':method:PUT'-p : 设置请求权重--no-dep : 不发送依赖信息
官方文档,使用nghttpd,可以架设一个简易的支持HTTP2的服务器。打开附件中的示例文件,在路径demos/nghttp/
里有一个run.sh(需要以管理员权限执行)
$ cd demos/nghttp
$ sudo ./run.sh [server-push]
就可以执行提供的shell脚本,第三个参数server-push是可选的,如果不需要server-push功能,直接sudo ./run.sh
就可以了,在本地设置好host 127.0.0.1 http2test.com
后,就可以在本地浏览器中访问了,路径是https://http2test.com/examples/dashboard/
。由于自签的证书不被信任,需要安装公钥keys/*.crt
文件并信任(Mac中需要打开keychain这个软件,然后找到刚安装的证书,改为总是信任),安装信任后,打开Chrome就不会提示警告了;
如果需要开启server-push功能,输入指令sudo ./run.sh server-push
,shell脚本里设置了-p/examples/dashboard/=/examples/dashboard/d3.js
,当请求路径/examples/dashboard/
时,就推送/examples/dashboard/d3.js
文件。
nginx的conf文件里的设置,设置完后需要sudo nginx -s reload
,由于nginx的ngx_http_v2_module
模块是替代以前的ngx_http_spdy_module
,故开启HTTP2支持后,就无法同时开启SPDY支持:
server { listen 443 ssl http2; server_name http2test.com; ssl_certificate $证书地址; ssl_certificate_key $证书私钥地址; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { root $静态文件地址; index index.html index.htm;
}
}
这里引入下Server Push的概念,在上一篇概念篇有介绍道,Server Push就是,当请求时,服务器同时将其它文件也推送过来,HTTP2的说明中,并没有规定Server Push具体的实现策略,这个可由服务器和客户端自主决定:
这里着重介绍h2o,为什么h2o要这么做呢?因为流量就是钱啊。。。客户端接收到服务器端发送的PUSHPROMISE Frame
后,可以通过是否发送RST Frame
来拒绝掉服务器推送的文件,但是在这个过程中,服务器推送的文件可能已经发送一部分过来了,如果客户端决定不接收服务器推送的文件,那么之前发送的部分就是浪费掉的流量,h2o就是为了解决这个问题;在设置好h2o后,我们来看看第一次访问的结果:
让我们来看看第二次访问的结果:
可以看到,第二次访问时,并没有PUSHPROMISE Frame
了,也就是说,服务器不再推送文件了。
这里讲解基本的wireshark使用,由于大多数的HTTP2都是基于Over TLS版,也就是需要解密才能看到正确的HTTP2包内容;wireshark提供了两个方式:
~/.bashprofile
:
export SSLKEYLOGFILE=$刚配置的地址 source ~/.bashprofile
SSLKEYLOGFILE
:
open -a Google\ Chrome配置好解密方式后,使用浏览器打开刚跑起来的HTTP2 Demo,https://http2test.com/examples/dashboard/
,然后再筛选出输入http2并回车,就可以看到筛选的HTTP2包。
fiddler
fiddler解密HTTPS采用的是中间人攻击的方法,客户端访问到fiddler代理服务器,然后fiddler代理服务器再模拟客户端访问到网站。那么,需要安装Fiddler的根认证证书,同时启用HTTPS解密功能。
对于Fiddler来说,对于每个机器其实下发的其实是不同的根证书,所以,一定要通过Fiddler的配置网页来安装证书(配置代理后,打开网站http://ipv4.fiddler:8888
,然后点击图中的FiddlerRoot Certificate
来下载安装)
针对iOS及部分高版本的Android,证书中的部分信息是必要的,有两个办法。1) 采用如图所示的设置(iOS 9亲测可用),2) 安装使用Fiddler CertMaker插件
然后需要介绍的一个点是FiddlerScript,这是一个很强大的功能。如果需要将以前调试HTTP的方法在HTTPS同样适用,需要有这些设置。部分设置可以参考Qzone https+SPDY实施 扩展篇:debug with https,然后之后我也会继续更新Fiddler调试部分的内容,请收藏哦。在Willow的rule里设置HTTPS -> HTTP; HTTPS -> File; 都是可以生效的。
如果需要HTTPS -> HTTPS 或者 HTTPS -> HTTP,可以采用上面链接的方法,也可以设置oSession["x-replywithtunnel"] = "FakeTunnel"
。
if (oSession.HostnameIs("app.yourdomain.com"))
{ // Handle CONNECT Tunnels
if (oSession.HTTPMethodIs("CONNECT"))
{
oSession["x-replywithtunnel"] = "FakeTunnel"; return;
}
oSession.fullUrl = "http://somedomain:someport" + oSession.PathAndQuery;
}
首先来看本地搭建的HTTPS服务:
*.http2test.com
,他们的TCP Stream序号都是30,复用同一条TCP连接
那么,如果域名不同,甚至都不是子域名的方式,也可以通过这一个点去优化吗?是可以的,通过证书里的subjectAltName,我们可以在证书里管理多个不同域名,例如google的证书:
不过这样证书很贵就是了 - -
HSTS主要是为了改善以下几个问题:
要开启HSTS,只需要服务器端在返回头中返回(不支持IP)
Strict-Transport-Security: max-age=31536000 [; includeSubDomains] [; preload]# includeSubDomains 可以开启子域名的HSTS功能# 不过需要评估这么做的是否会有负面影响
在max-age指定的时间内,浏览器都自动以HTTPS访问。如果需要关闭HSTS,返回max-age=0
就可以了。
CSP是一个声明式的安全机制,原本的目的是防止XSS跨站脚本攻击。例如,CSP可以完全禁止内联的Javascript代码的执行,控制哪些外联的Javascript文件可以被执行。
Content-Security-Policy: default-src 'self'; img-src *; script-src scripts.example.com
上面的CSP策略是,默认允许本源的资源的访问,允许所有URL的图片显示,外链脚本只允许来自于scripts.example.com
的文件执行;
Content-Security-Policy: default-src https: 'unsafe-inline' 'unsafe-eval'; connect-src https:
上面的CSP策略是,默认允许来自于任何URL的资源,只要它是安全的HTTPS,同时开启了内联JS和eval的执行(默认是被CSP禁止的),connect-src
设定了Ajax请求只能请求HTTPS;(部分开启内联JS的执行可参考Content Security Policy Level 2 介绍)
使用report-uri
指令,违反CSP策略的行为都会被上报到该CGI