微信图片另存失败?这得从 SNI 说起

一、从一个微信 Bug 说起

问题:在微信 Android 客户端,一张七牛 CDN 上的 HTTPS 链接的图片,用微信浏览器打开可以正常访问,但是,长按图片保存时,却提示下载图片失败。

发现这个问题后,我用了一个简单的测试用例,很快就定位到问题原因。

从这个测试用例可以看出,只有七牛 CDN 上 HTTPS 链接的图片,长按保存才会失败。有了这个测试用例,我就猜测问题可能是 HTTPS 证书的原因。

于是,我就把这个问题报给了七牛,七牛排查之后,问题果然是 HTTPS 证书返回不对引起的。那么,是什么原因导致七牛返回的 HTTPS 证书不对呢?请继续看下文。

在了解问题的本质原因之前,我们先来了解一些相关的背景知识。

二、背景知识

在早期的服务器部署方案中,一台服务器(或者说一个 IP)只会提供一个服务,所以在 SSL 握手时,服务器端可以确认客户端申请的是哪张证书,因为证书只有一张,服务器只要把这张证书发送给客户端就可以了。如下图所示:

一台物理机,一个 IP,一张 HTTPS 证书,并且提供了唯一的一个服务(站点),所以,不管哪个客户端访问这个服务,服务器都返回唯一的证书。

后来,服务器性能提高了,一台服务器只部署一个服务就有点浪费了,所以就出现了虚拟主机,这样服务器虽然只有一个,但可以在每个虚拟主机上都部署一个服务,这就造成了一个 IP 会对应多个域名的情况。解决方案有一些,例如申请泛域名证书,对所有 *.yourdomain.com 域名都可以认证,但如果你有另外一个域名 *.yourdomain.cn,那就不行了。如下图所示:

在早期的 HTTPS 协议中,当客户端发起 SSL 握手时,客户端并不会将 host 信息带过来,所以服务器就不知道客户端访问的是哪个域名服务,服务器也就不知道把哪张证书返回给客户端了,出现这种情况时,服务器通常是把默认的一张证书返回给客户端。

最后我们得出的问题关键是:

客户端发起 SSL 握手时,缺少 Host 信息。

所以,那些制定网络协议的人,又提出了 SNI。

SNI(Server Name Indication)定义在 RFC 4366,是一项用于改善 SSL/TLS 的技术,在 SSLv3/TLSv1 中被启用。它允许客户端在发起 SSL 握手请求时(具体说来,是客户端发出 SSL 请求中的 ClientHello 阶段),就提交请求的 Host 信息,使得服务器能够切换到正确的域并返回相应的证书。

检查浏览器是否支持 SNI:https://sni.velox.ch/

另外,不同的是,在 HTTP 协议中,客户端发起一个网络请求,客户端都会把主机头 host 信息放在 request header 中传给后台,这样服务器就知道客户端访问的是哪个域名,服务器只要把请求转向相应的服务就可以了。例如下图所示的一个请求,我们可以看到 request header 中,带有 host 信息。

最后找微信的人确认,长按保存图片时,微信确实没有把 host 信息传给后台,这是微信图片下载库的一个 bug。

那么,微信要解决这个问题,必须要等下一个版本发布了,有没有其他的解决方案呢?

七牛给出了一个解决方案,因为我们的图片都是存放在域名 dn-kdt-img.qbox.me 下面的,而这个域名又是七牛自己的,所以七牛就把所有域名 *.qbox.me 下的所有节点服务器,都配置了唯一的一张 HTTPS 证书,当客户端访问这些节点服务器的时候,服务器只要返回这张唯一的证书就可以了。如下图所示:

不管用户访问的是哪个节点服务器,服务器都只返回这张唯一的证书,这样处理之后,在建立 SSL 连接时,不管客户端是否发送了 host 信息给服务器,服务器都能返回正确的证书。

三、心得体会

1、作为一个前端工程师,了解一些运维的知识还是很有必要的,因为我们在做网站性能优化的时候,经常需要用到运维相关的技能及知识。

2、前端是一个非常有挑战的岗位,可涉及的知识面会很广,往前看,你可以了解一些产品,交互方面的知识,这样可以跟用户靠的更近;往后看,你可以了解一些后台的知识,这样在跟后台提接口要求的时候,可以沟通更顺畅;往上看,你可以让自己知识面更广,这样你可以看问题更加全面,所谓“站得高,望得远”便是这样的道理;忘下看,你可以深入去了解某一块专业知识,这样你可以成为这块领域的专家。

参考文档: