大型网站的 HTTPS 实践(四)——协议层以外的实践
url: http://op.baidu.com/2015/04/https-s01a04/
1 前言
网上介绍 https 的文章并不多,更鲜有分享在大型互联网站点部署 https 的实践经验,我们在考虑部署 https 时也有重重的疑惑。
本文为大家介绍百度 HTTPS 的实践和一些权衡, 希望以此抛砖引玉。
2 协议层以外的实践工作
2.1 全站覆盖 https 的理由
很多刚接触 https 的会思考,我是不是只要站点的主域名换了 https 就可以?答案是不行。
https 的目的就是保证传输过程的安全,如果只有主域名上了 https,但是主域名加载的资源,比如 js,css,图片没有上 https,会怎么样?
从效果上来说,没有达到保证网站传输过程安全的目的,因为你的 js,css,图片仍然有被劫持的可能性,如果这些内容被篡改 / 嗅探了,那么 https 的意义就失去了。
浏览器在设计上早就考虑的这样的情况,会有相应的提示。具体的实现依赖浏览器,例如地址栏锁形标记从绿色变为黄色, 阻止这次请求,或者直接弹出非常影响用户体验的提示 (主要是 IE),用户会感觉厌烦,疑惑和担忧安全性。
很多用户看见这个链接会习惯性的点”是”,这样非 https 的资源就被禁止加载了。非 ie 的浏览器很多也会阻止加载一些危害程度较高的非 https 资源(例如 js)。我们发现移动端浏览器的限制目前会略松一些。
所以这里要是没做好,很多情况连网站的基本功能都没法正常使用。
2.2 站点的区别
很多人刚接触 https 的时候,觉得不就是部署证书,让 webserver 支持 https 就行了吗。
实际上对于不同的站点来说,https 的部署方式和难度有很大的区别。对于一个大型站点来说,让 webserver 支持 https,以及对 webserver 在 https 协议特性上做一些优化,在迁移的工作比重上,可能只占到 20%-40%。
我们考虑下以下几种情况下,部署 https 的方案。
2.2.1 简单的个人站点
简单的定义:资源只从本站的主域或者主域的子域名加载。
比如 axyz 的个人 blog,域名是 axyzblog.com。加载主域名下的 js 和图片。
这样的站部署 https,在已有证书且 webserver 支持的情况下,只需要把主域名替换为 https 接入,然后把资源连接修改为 https:// 或者 //。
2.2.2 复杂的个人站点
复杂的定义:资源需要从外部域名加载。
这样就比较麻烦了,主域资源容易适配 https,在 cdn 上加载的资源还需要 cdn 服务商支持 https。目前各大 cdn 的服务商正在逐渐提供 https 的支持,需要迁移的朋友可以看看自己使用的 cdn 是否提供了这项能力。一些 cdn 会对 https 流量额外收费。
Cdn 使用 https 常见的方案有:
1 网站主提供私钥给 cdn,回源使用 http。
2 cdn 使用公共域名,公共的证书,这样资源的域名就不能自定义了。回源使用 http。
3 仅提供动态加速,cdn 进行 tcp 代理,不缓存内容。
4 CloudFlare 提供了Keyless SSL的服务,能够支持不愿意提供私钥, 不想使用公共的域名和证书却又需要使用 cdn 的站点了。
2.2.3 简单的大型站点
简单的定义: 资源只从本站的主域, 主域的子域,或者自建 / 可控的 cdn 域名加载,几乎没有第三方资源。如果网站本身的特性就如此,或愿意改造为这样的类型,部署 https 就相对容易。Google Twitter 都是非常好的范例。优点:已经改成这样的站点,替换 https 就比较容易。缺点:如果需要改造,那么要很大的决心,毕竟几乎不能使用多样化的第三方资源了。
2.2.4 复杂,访问速度重要性稍低的大型站点
复杂的定义:从本站的非主域,或者第三方站点的域名有大量的第三方资源需要加载,多出现在一些平台类,或者有复杂内容展现的的网站。
访问速度要求:用户停留时间长或者强需求,用户对访问速度的耐受程度较高。比如门户,视频,在线交易类(比如火车票 机票 商城)网站。
这样的站点,可以努力推动所有相关域名升级为支持 https。我们用下图举例说明下这样修改会导致一个网站的链接发生怎样的改变。
负责流量接入的团队将可控的接入环境改造为 http 和 https 都支持,这样前端工程的工作相对就少一些。大部分时候将链接从 http:// 替换为 // 即可. 在主域名是 https 的情况下,其它资源就能自动从 https 协议下加载。一些第三方资源怎么办?一般来说只有两种选择,一迁移到自己的 cdn 或者 idc 吧,二强制要求第三方自己能支持 https。
以全站 https 接入的 facebook 举例。第三方厂商想在 facebook 上线一个游戏。facebook:请提供 https 接入吧。第三方想:能赚钱啊,还是提供下 https 接入吧。所以,足够强势,有吸引力,合作方也有提供 https 的能力的话,这是完全可行的。如果你的平台接入的都是一些个人开发者,而且还赚不到多少钱的情况下,这样就行不通了。
优点:前端改动相对简单,不容易出现 https 下还有 http 的资源问题。
缺点:通常这样的实现下,用户的访问速度会变慢,比如从 2.5 秒变为 3 秒,如上述的理由,用户还是能接受的。对第三方要求高。
2.2.5 复杂,访问速度有严格要求的大型站点
复杂的定义:同上。
访问速度要求:停留时间不长,用户对访问速度的心理预期较高。
但是如果用户把网站当作工具使用,需要你很快给出响应的时候,这样的实现就不好了。后续几个部分我们介绍下这些优化的抉择。
2.3 域名的选择
域名对访问速度的影响具有两面性:域名多,域名解析和建立连接的时间就多;域名少,下载并发度又不够。
https 下重建连接的时间成本比 http 更高,对于上面提到的简单的大型站点, 可以用少量域名就能满足需求,对于百度这样富展现样式较多的搜索引擎来说,页面可能展示的资源种类太多。而不同类型的资源又是由不同的域名 (不同的产品 或者第三方产品) 提供的服务,换一个词搜索就可能需要重新建立一些资源的 ssl 链接,会让用户感受到卡顿。
如果将域名限制在有限的范围,维持和这些域名的连接,合并一些数据,加上有 spdy,http2.0 来保证并发,是可以满足我们的需求的。
2.4 连接复用
连接复用率可以分为 tcp 和 ssl 等不同的层面,需要分开进行分析和统计。
2.4.1 连接复用的意义
HTTP 协议 (RFC2616) 规定一个域名最多不能建立超过 2 个的 TCP 连接。但是随着互联网的发展,一张网页的元素越来越多,传输内容越来越大,一个域名 2 个连接的限制已经远远不能满足现在网页加载速度的需求。
目前已经没有浏览器遵守这个规定,各浏览器针对单域名建立的 TCP 连接数如下:
浏览器 | 连接数 |
Firefox 2 | 2 |
Firefox 3+ | 6 |
Chrome | 6 |
Ie10 | 8 |
IE8 | 6 |
Safari 5 | 6 |
Opera 12 | 6 |
表格 1 浏览器单域名建立的最大并发连接数
从上表看出,单个域名的连接数基本上是 6 个。所以只能通过增加域名的方式来增加并发连接数。在 HTTP 场景下,这样的方式没有什么问题。但是在 HTTPS 连接下,由于 TLS 连接建立的成本比较高,增加并发连接数本身就会带来较大的延迟,所以对域名数需要一个谨慎的控制。
特别是 HTTP2 即将大规模应用,而 HTTP2 的最大特性就是多路复用,使用多个域名和多个连接无法有效发挥多路复用和压缩的特性。
那 HTTPS 协议下,一张网页到底该有多少域名呢?这个其实没有定论,取决于网页需要加载元素个数。
2.4.2 预建连接
既然从协议角度无法减少握手对速度的影响,那能不能提前建立连接,减少用户可以感知的握手延迟呢?当然是可以的。思路就是预判当前用户的下一个访问 URL,提前建立连接,当用户发起真实请求时,TCP 及 TLS 握手都已经完成,只需要在连接上发送应用层数据即可。
最简单有效的方式就是在主域下对连接进行预建,可以通过请求一些静态资源的方式。但是这样还是不容易做到极致,因为使用哪个连接,并发多少还是浏览器控制的。例如你对 a 域名请求一个图片,浏览器建立了两个连接,再请求一张图片的时候,浏览器很大概率能够复用连接,但是当 a 域名需要加载 10 个图片的时候,浏览器很可能就会新建连接了。
2.4.3 Spdy 的影响
Spdy 对于连接复用率的提升非常有效,因为它能支持连接上的并发请求,所以浏览器会尽量在这个链接上保持复用。
2.4.4 其它
也可以尝试一些其他发方法,让浏览器在访问你的网站之前就建立过 https 连接,这样 session 能够复用。HSTS 也能有效的减少跳转时间,可惜对于复杂的网站来说,开启需要考虑清楚很多问题。
2.5 优化的效果
从百度的优化经验来看看,如果不开启 HSTS,用户在浏览器直接访问主域名,再通过 302 跳转到 HTTPS。增加的时间平均会有 400ms+,其中 302 跳转和 ssl 握手的因素各占一半。但是对于后续的请求,我们做到了对绝大部分用户几乎无感知。
这 400ms+ 还有很多可以优化的空间,我们会持续优化用户的体验。
3 HTTPS 迁移遇到的一些常见问题。
3.1 传递 Referrer
我们可以把自己的网站替换为 https,但是一般的站点都有外链,要让外链都 https 目前还不太现实。很多网站需要从 referrer 中判断流量来源,因此对于搜索引擎这样的网站来说,referer 的传递还是比较重要的。如果不做任何设置,你会发现在 https 站点中点击外链并没有将 referrer 带入到 http 请求的头部中(http://tools.ietf.org/html/rfc7231#section-5.5.2)。现代的浏览器可以用 meta 标签来传递 refer。(http://w3c.github.io/webappsec/specs/referrer-policy)
<meta name=”referrer” content=”always”> 传递完整的 url
<meta name=”referrer” content=”origin”> 只传递站点,不包含路径和参数等。
对于不支持 meta 传递 referrer 的浏览器,例如 IE8, 我们怎么办呢?
可以采用再次跳转的方法,既然 HTTPS 下不能给 HTTP 传递 referer,我们可以先从 HTTPS 访问一个可控的 http 站点,把需要传递的内容放到这个 http 站点的 url 中,然后再跳转到目标地址。
3.2 form 提交
有时需要将 form 提交到第三方站点,而第三方站点又是 http 的地址,浏览器会有不安全的警告。可以和 referrer 的跳转传递采取相似的逻辑。
但是这样对 referer 和 form 等内容的方案,并不是完美的解决方法,因为这样还是增加了不安全的因素(劫持,隐私泄露等 )。理想情况需要用户升级符合最新规范的浏览器,以及推进更多的站点迁移至 https。
3.3 视频播放
简单来说,如果你使用 http 的协议来播放视频,那么浏览器仍然会有不安全的提示。所以你有两种选择,1 让视频源提供 https。2 使用非 http 的协议,如 rtmp 协议。
3.4 用户异常
在 https 迁移的过程中,也会有不少热心的用户向我们反馈遇到的各种问题。
常见的有以下的一些情况:
1 用户的系统时间设置错误,导致提示证书过期。
2 用户使用 fiddler 等代理进行调试,但是没有添加这些软件的根证书,导致提示证书非法。
3 用户使用的 Dns 为公共 dns 或者跨网设置 dns,一些请求被运营商作为跨网流量拦截。
4 连通性有问题,我们发现一个小运营商的 https 失败率奇高,又没法联系到他们,只能不对他们进行 https 的转换。
5 慢。有时由于网络环境的因素,用户打开其他网站也慢,ping 哪个网站都要 500-2000ms。这时 https 自然也会很慢。
4 结束语
对于复杂的大型网站来说,HTTPS 的部署有很多工作要完成。
面对困难和挑战,有充足的动力支持着我们前进:https 上线后,劫持等原因导致的用户功能异常,隐私泄露的反馈大幅减少。
热心的用户经常会向我们反馈遇到的各种问题。在以前,有时即使我们确定了是劫持的问题,能够解决问题的方法也非常有限。每当这种时候,自己总会产生一些无力感。
HTTPS 的全站部署,给我们提供了能解决大部分问题的选项。能让一个做技术的人看到自己的努力解决了用户的问题,这就是最棒的收获。
HTTPS 没有想像中难用和可怕,只是没有经过优化。与大家共勉。