让多说评论框完美支持HTTPS

多说将于2017年6月1日正式关停服务,感谢一路陪伴,再见!

前几天把博客的SSL证书部署好了,首页已经可以完美支持HTTPS了,但是进入文章页面以后,Chrome的小绿锁会变黄并且给出三角形警告。查了一下原因,是因为博客使用了多说评论框引起的。虽然多说自己的资源已经支持全部HTTPS了,但是用户头像、表情等资源是引用的站外文件,例如新浪微博的表情就不支持HTTPS,因此导致了浏览器警告该网页包含不安全的资源。作为一个强迫症患者,我当然不能容忍完美的小绿锁被多说破坏。经过在网上的一番摸索之后,终于搞定了多说。下面就分享一下我的方法。

思路

首先我想到的是通过Nginx反向代理,将所有的HTTP协议简单粗暴的替换为HTTPS,然而我发现新浪微博的表情不支持HTTPS,直接替换为HTTPS会提示证书不匹配。顺便看了下他们的证书,竟然是颁发给kyfw.12306.cn的…

然后谷歌了一下,看到了Hack0nAir和Jerry Qu的文章,发现他们的思路是搭建一个代理服务器,通过https://example.com/proxy/xxx的方式,将原本不支持HTTPS的网址xxx,变成以https://example.com开头的,支持HTTPS的网址。

另外我也搞清楚了,多说评论框中不支持HTTPS的地方主要有三处:

  1. 用户头像;
  2. 评论内容中的表情;
  3. 发表评论时供用户选择输入的表情。

在弄清楚问题的根源以后,我便开始计划如何实施。与那两篇文章不同的是,他们将多说的embed.js下载到本地,修改好了以后再托管到自己的服务器上。但是这样的话,当多说升级版本时,就得重新下载新版本,再次修改,比较麻烦。所以此处我用到了Nginx的http_sub_module,直接通过反向代理多说,然后替换掉embed.js中不支持HTTPS的内容。这样做的好处是可以同步多说官方的版本更新,但是当多说的embed.js变动较大时,可能需要重新修改Nginx配置文件。

具体步骤

按照之前的思路,我决定搭建两个代理服务器,一个用来代理多说官方的embed.js文件,一个用来代理不支持HTTPS的资源。

搭建HTTPS代理服务器

假设某个用户头像的图片地址为http://s.cn/x.jpg,并且该网址不支持HTTPS,首先需要搭建一个代理服务器来解决该问题。

假设此处使用的域名为duoshuo.example.com,当访问域名/proxy/s.cn/x.jpg时,通过反向代理能够获取到该用户的头像,这时访问的地址就已经是HTTPS的了,问题也就解决了。

具体做法是,先给duoshuo.example.com配置好SSL证书,然后在Nginx的配置文件中添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 443 ssl;
...
...
...
# HTTPS反向代理
location ~ "^/proxy/(.*)$" {
resolver 8.8.8.8;
proxy_pass http://$1;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $proxy_host;
}
}

由于我自己的代理使用了CDN,缓存、referer限制等操作已经在CDN上面设置好了。如果你不打算使用CDN,那么还可以在配置文件里面配置好缓存和referer限制等,以便加快访问速度及安全性(详见本文末尾Jerry Qu的博文)。

搭建多说反向代理服务器

此处继续使用duoshuo.example.com这个域名,接下来需要搭建一个多说的反向代理服务器,当访问域名/embed.js时,读取多说官方的embed.js并替换掉其中不支持HTTPS的内容。

注意:由于此处需要使用http_sub_modulesub_filter来替换多处内容,而Nginx 1.9.4版本才支持同时使用多个sub_filter查看更新记录】,因此,请确保你的Nginx版本大于或等于1.9.4。

首先要找出多说embed.js中不支持HTTPS的地方。先从多说官方下载最新的embed.js文件,然后利用工具将Javascript代码格式化,方便修改。

然后需要做三处修改。第一处是用户头像。搜索e.avatar_url,找到以下语句:

1
2
3
avatarUrl:function(e) {
return e.avatar_url || rt.data.default_avatar_url;
}

此处代码是返回用户的头像(已登录)或者默认头像(未登录)的图片地址,默认是不支持HTTPS的。因此需要在返回头像的图片地址之前,加上前一步搭建的HTTPS代理服务器网址。这个过程要在return语句之前完成:

1
2
3
4
5
6
7
8
9
avatarUrl:function(e) {
/* 将http://替换为https://duoshuo.example.com/proxy/ */
if (e.avatar_url) {
e.avatar_url = e.avatar_url.replace(/^http\:\/\//, "https://duoshuo.example.com/proxy/");
} else {
rt.data.default_avatar_url = rt.data.default_avatar_url.replace(/^http\:\/\//, "https://duoshuo.example.com/proxy/");
}
return e.avatar_url || rt.data.default_avatar_url;
}

第二处修改是新浪微博的表情。目前所有新浪微博的表情都是http://img.t.sinajs.cn/开头,所以只需替换掉这个网址即可。

第三处则是评论内容中的表情,它存在于s.message中。这里可以类似于第一处那样,直接替换掉http://就可以。在r=s.author;之后添加以下语句即可进行替换。:

1
2
3
...
r=s.author;
s.message = s.message.replace(/src=\"http\:\/\//,'src=\"https://duoshuo.example.com/proxy/');

在弄清楚要替换的内容以后,就可以将其写进Nginx配置文件。当通过该代理访问embed.js时就可以自动进行替换不支持HTTPS的部分。在之前的配置文件中,添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server {
listen 443 ssl;
...
...
...
# HTTPS反向代理
location ~ "^/proxy/(.*)$" {
...
}
# 多说反向代理
location / {
# 替换用户头像
sub_filter 'return e.avatar_url||rt.data.default_avatar_url' 'if(e.avatar_url){e.avatar_url=e.avatar_url.replace(/^http\:\/\//,"https://duoshuo.example.com/proxy/")}else{rt.data.default_avatar_url=rt.data.default_avatar_url.replace(/^http\:\/\//,"https://duoshuo.example.com/proxy/")};return e.avatar_url||rt.data.default_avatar_url';
# 替换新浪微博表情
sub_filter 'http://img.t.sinajs.cn/' 'https://duoshuo.example.com/proxy/img.t.sinajs.cn/';
#替换评论中的表情
sub_filter 'r=s.author;' 'r=s.author;s.message = s.message.replace(/src=\"http\:\/\//,\'src=\"https://duoshuo.example.com/proxy/\');';
sub_filter_once on;
# 指定需要被替换的MIME类型为所有类型
sub_filter_types *;
resolver 8.8.8.8;
# 设置后端不压缩文件,否则无法替换
proxy_set_header Accept-Encoding "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $proxy_host;
proxy_pass https://static.duoshuo.com/;
}
}

这里有一点需要注意,如果后端返回的文件是已经 gzip 压缩过的文件,因为需要解压缩,然后再压缩,sub_filter 不支持gzip,所以无法替换。为了避免此种情况,我们需要后端不压缩文件,做法就是去除 HTTP 请求头中的 压缩头,来指导后端不压缩:

1
proxy_set_header Accept-Encoding "";

配置完成以后,重启Nginx,然后将原来引用多说文件的地方换成duoshuo.example.com/embed.js,再刷新网页,看看烦人的小黄锁是不是消失了~

提示:截至本文发布时,多说的版本号为15.9.28,由于本文具有一定时效性,当你看到此文章时版本号可能已经更迭。请按照实际情况做以上修改,不要照搬代码


参考文章:

Acris Liu wechat
关注Mr.X微信公众号
打赏,是超越赞的一种表达。