Apache + WordPress + SSL 完全指南

似乎不少使用国外主机的站长都想弄个 https:// “玩”,但是许多人对 SSL/TLS、HTTPS、证书等概念了解有限,而中文互联网上相关的教程也不是很完备,各种杂乱。正好,本人这几天花了点时间研究了一下,给自己的站也部署了 HTTPS,写成这篇《Apache + WordPress + SSL 完全指南》,以飨中文读者。本文较长,但是我会尽量写得清楚明白的。

Apache + WordPress + SSL 完全指南

一、基本术语及证书原理介绍

本部分有点枯燥,不愿意读的读者可以直接跳过。以下出现的缩写鼠标悬浮于上可查看全称。

HTTPS

HTTPS 就是“超文本安全传输协议”,通俗地说就是建立在 SSL/TLS 上的 HTTP。

SSL/TLS

理论上讲这是两个协议,后者是前者的继任者,但其实 SSL 3.0 和 TLS 1.0 的差异很小,所以两者很多时候是混为一谈的……这两个都是传输层协议,在他们的基础上可以建议应用层的协议如 FTP 和 Telnet,上面说的 HTTPS 就是建立在 SSL/TLS 基础上的 HTTP。

证书

这里的证书主要指使用公私钥对加密的证书。下面主要会涉及到两种证书,一个是 S/MIME 证书,另一个就是 SSL/TLS 证书了。前者可以用在电子邮件上,这里用作“服务器身份识别”,可以理解为取代传统的“用户名+密码”的认证方式的一把“钥匙”。后者一般用于服务器加密(网页、邮件服务等),也就是我们部署 HTTPS 时所必须的。

本文出现的证书都是用公私钥加密的证书。这涉及到 [[非对称加密]] 技术,每张证书都由一个公钥和一个私钥组成,两个拼在一起才是一套。其中,正如它们的名字所暗示的,公钥是可以随便发给别人看的,而私钥一定是要保密的,如果私钥被偷了,后果很严重。

信任链与 CA

CA 就是“数字证书认证中心”,是证书的签发机构。证书的世界里有一套信任体系,信任了一家机构之后,此机构签发的证书都是受信的。在我们的操作系统和浏览器中,内置了几家靠谱的 CA 的根证书,并列为可信任的证书。而这些 CA 签署的证书,由于信任链的关系,也是默认被信任的。比如中国招商银行的网银页面就是用的 VeriSign 签署的的证书。由于 VeriSign 是一家老牌的靠谱 CA,大部分操作系统和浏览器默认都是信任 VeriSign 的根证书的,当然也就默认信任招商银行用的证书了。

如果有一家 CA,比较年轻,发展得还不够大,那么就会面临有的地方信任,有的地方不信任的问题。比如 CACert.org 就是这样一家 CA。这是一家靠社区驱动的 CA,它的根证书受到 Arch Linux 等一些 Linux 发行版的信任,但是却不受 Windows 和 OS X 的信任(完整的受信任列表见这里),所以它的网站(用了它自己的证书)若是用 https:// 浏览的话(点击这里),一些 Linux 用户会毫无压力地进入,而 Windows 和 OS X 用户则会看到证书警告说该网站的证书不受信任。以下两张截图便显示了这一点:

Apache + WordPress + SSL 完全指南

Apache + WordPress + SSL 完全指南

而如果有一家 CA 完全就像街头的“办证”广告那样完全胡来,那自然是没有任何一家去信任它的证书了,比如下面这个:

Apache + WordPress + SSL 完全指南

大家都懂的……

不过需要说明的是,如果你使用自签名证书的话,对用户来说默认都是不受信任的。

二、部署 HTTPS 的前提条件

以下是部署 HTTPS 的前提:

  • 最好是 VPS 甚至独立服务器。如果是共享主机的,则需要主机商提供 SSL/TLS 功能(有的面板有这样的功能),否则不行。
  • 服务器上必须开启了 mod_ssl。(共享主机的话,主机商一般都开启了)
  • 最好有独立 IP。如果没有,则需要主机打开 SNI 功能(见 RFC 4366),但 XP + IE8 及以下是不支持 SNI 的,会报证书错误。(PS:Showfom 注,也有比较高级的证书是支持多域名的,比如 CloudFlare 在使用的 GlobalSSL ,如图
  • 最好去申请/购买一张正规的第三方知名 CA 签署的证书,不要像铁道部那样胡乱搞一个。不过,如果你只是为了测试用途或是对网站数据进行特殊加密自己私下使用的话(你懂的),自签名的证书也是可以的。
  • 推荐有一个 Linux 运行环境。如果已经是 Linux 服务器的话,直接 SSH 进服务器执行命令也是可以的。

三、自签名证书

虽然不一定受信,但是证书的确是人人都可以自己制作的。有的时候,我们使用 HTTPS 只是为了数据的加密,仅供自己使用,不用管别人信任与否,这时候就可以用到自签名证书。在这种情况下,你可以把自己创建的证书加入到自己的桌面电脑的信任列表,这样就不会再有错误提示了。

Apache 的文档中有 Creating self-signed certificates 这一节教你怎么创建自签名证书,简单明了,十分傻瓜:

用包管理器安装 ssl-cert 这个包,再执行

sudo make-ssl-cert /usr/share/ssl-cert/ssleay.cnf /path/to/cert.pem

命令,按照提示输入一个 Common Name(CN,保持和主机名相同),便能按照 /usr/share/ssl-cert/ssleay.cnf 这个默认模板生成一份证书,这份证书是公钥和私钥合并在一个文件里的,用文本编辑器打开将看到这样的结构:

前面那段是私钥,后面那段是证书信息和公钥。这份证书默认的有效期是 3650 天(十年)。

其实刚才那个傻瓜式的命令实质上是执行了这个:

sudo openssl req -new -x509 -days 3650 -nodes -out /path/to/cert.pem -keyout /path/to/cert.pem

各参数的意义:

  • openssl req -new:用 OpenSSL 套件生成新的证书请求
  • -x509:使用 X.509(PEM 编码的一种实现)
  • -days 3650:有效期是十年,可以自己改
  • -nodes:这个不是英文单词 “nodes”,而是 “No DES” 的意思,代表不用 DES 加密私钥,否则私钥默认是用 DES 加密的,每次启动 Apache 要输入密码
  • -out:证书(包括证书信息和公钥)的文件名
  • -keyout:私钥的文件名,如果此项和上一项同名的话,那么公钥和私钥是合并在同一文件里的(正如上面所贴的)

执行这个命令会让你输入一系列信息,比如 Common Name (CN)、公司名、地理位置、联系方式等,除了 CN 必须要和网站域名匹配之外,其他可以按照你的喜好自由填写。

证书生成好后,便是在 Apache 的配置文件中指定它了。具体的配置文件写法可以参考它自带的 /etc/apache2/sites-available/default-ssl 配置文件,主要是以下这几行要修改:

并且如果按照上文,把公钥和私钥放在同一个文件里的话,那只要提供 SSLCertificateFile 就好了,后面的 KeyFile 不用填(保持它们的注释状态)。

插播:Linux 是不分辨文件扩展名的,所以理论上上面的这些公钥婆钥私钥的扩展名都是可以随便起的,甚至不填也可以,但是为了方便自己弄清楚,我习惯上是这样命名的:只包含证书信息和公钥的文件扩展名为 crt,同时包含了公钥和私钥的文件扩展名为 pem,只包含私钥的文件扩展名为 key

保存配置文件后,用 sudo a2ensite 来启用它,再执行一下 sudo a2enmod ssl 确保 mod_ssl 已经开启(如果使用虚拟主机的话,还需要在 apache2.conf 加上 NameVirtualHost *:443 这一条),然后执行 sudo service apache2 restart 来重启 Apache。如果没有出现奇怪的错误的话,那么现在你的网站已经可以通过 https:// 协议访问了。当然,由于这个证书是不受信任的,浏览器会弹出安全警告,不用管,虽然证书是不受信任的,但是毕竟是 TLS,所以传输的内容已经是加密过的了,第三方无法知道你访问的网站内容。如果不想每次都看到证书警告的话,可以把这个证书导入到系统中,设为信任。在 Firefox 中也可以选择“添加例外”。

四、第三方签名证书

虽然自签名证书默认是不受操作系统或浏览器的信任的,但是这并不影响它能够加密传输内容的本质。自签名证书给自己用是足够了,如果嫌证书警告烦人的话,自己添加为信任就好了,甚至在一小撮人中用也是足够了——你把证书发给你的朋友们,让他们也设为信任,这样他们访问你的网站的时候也不会弹警告了。但是如果要全世界大部分人访问你的网站的时候都不会弹证书警告的话,就要找一些靠谱的第三方 CA,让它们帮你签名证书,由于大部分人的操作系统或浏览器都是信任这些靠谱 CA 的,所以由他们签名的你的证书也是受信任的,这样访问你网站的时候就不会弹证书警告了。

注意:第三方签名证书不代表比自签名证书的加密更强悍更可靠!TLS 的加密强度由加密的位数决定,与证书是谁签名的无关。找第三方签名只是为了不弹证书警告。如果你只是自己用或是一小撮人用的话(思路提醒:twip),用自签名就足够了。

要使用第三方签名证书,首先同样要创建私钥:

openssl genrsa 1024 > ssl.key

各参数含义:

  • openssl genrsa 1024:用 OpenSSL 来生成一个 1024 位 RSA 加密的私钥(Generate RSA),这个位数是可以改的
  • > ssl.key:通过重定向命令,把生成的私钥保存到 ssl.key 这个文件中

生成好了之后用一些图形化的证书查看器查看大概是这个样子的(当然,指纹肯定不一样……)

Apache + WordPress + SSL 完全指南

然后你也得告诉第三方 CA 你的 CN、地址、联系方式等,这时需要使用 Certificate Request,就是“证书请求”,它是私钥和证书信息的整合,把它交给第三方 CA,他们可以读取这个文件中的私钥和证书信息,然后帮你签名。这个 Certificate Request 文件的扩展名一般用 csr

在刚才私钥的基础上生成证书请求的命令是这个:

openssl req -new -key ssl.key > ssl.csr

执行了之后也会要求你输入证书的信息,然后把最终生成的结果放到 ssl.csr 中。如果用文本编辑器打开这个文件,看到的是类似这样的东西:(我省略了一部分字符)

把这个文件传给 CA,他们签名之后会给你颁发公钥证书,然后把它添加到 Apache 的配置文件中了:

因为这是第三方 CA 签名的,所以要有一个把你的证书和它的根证书相连接的过程。如下图:

Apache + WordPress + SSL 完全指南

那个 ChainFile 就是中间那一环。当然了,正如 KeyFile 可以和主证书文件合并一样,这个 ChainFile 也是可以和主文件合并的,这三个全部首尾相连地并成一个文件也是没有问题的,这样就只要填一个 SSLCertificateFile 就好了。

改好配置之后,像上面自签名那样,也重启一下 Apache,就可以用 https:// 协议访问了。并且,由于是大家都信任的靠谱 CA 签名的,这样的证书是不会弹证书警告的。

五、WordPress HTTPS 免插件部署指南

解决“混合内容”问题

这一节主要讲一下 WordPress 的 HTTPS 实现。在 WordPress 的插件库中,有一个叫 Force SSL 的插件,它的作用是当用户用 https:// 访问的时候,实时解析页面中所有的 URI,把 http:// 换成 https://。本来我觉得那插件还是不错的,后来我把 php 的内存限量稍微改小一些,就大量出现 500 错误,一看错误日志,竟然全是那个插件“试图分配超过限额的内存”,这才觉得这插件实在太耗资源了!所以我果断删除了那个插件,开始尝试无插件实现 WordPress 的 HTTPS 访问。

如果按照上面的教程,把证书什么的都设置好了的话,这时候 WordPress 已经可以通过 https:// 协议访问了,但是有问题:页面中的图片、CSS、JavaScript 的 URI 都是 http:// 的,所以会有“混合内容”的问题,在 Chromium/Chrome 里是一个难看的红叉叉(高危内容,如 JavaScript)或灰色的锁上有个黄三角(低危内容,如图片),在 Internet Explorer 里则是显示为“混合内容”,弹出大量烦人的警告窗口。那个 Force SSL 插件的两大作用之一就是把这种资源的 URI 改成 https:// 的。

Apache + WordPress + SSL 完全指南

但是,让我们手工来改吧。这里要介绍一下“协议级相对路径“,例子可以见 Google HTML/CSS Style Guide 的建议:

Omit the protocol from embedded resources.
Omit the protocol portion (http:, https:) from URLs pointing to images and other media files, style sheets, and scripts unless the respective files are not available over both protocols.
Omitting the protocol—which makes the URL relative—prevents mixed content issues and results in minor file size savings.

把资源的 URI 省略掉协议,就可以实现“相对协议”了,比如把一个链接写成://example.org/index.html,那么,如果是在一个 http:// 页面上点击这个链接,用户就会来到 http://example.org/index.html ,而如果是在一个 https:// 页面上点击这个链接,用户就会来到 https://example.org/index.html !这样完全省去了各种判断协议的代码,也大量节省了服务器资源

具体怎么实现呢?很简单,改主题……看看你的博客首页的源代码的头部和尾部,一般那些 CSS、JavaScript 就放在这里,认准它们的样子,再到主题文件里去找到它们对应的代码,比如源代码中有这样的:

<link rel="stylesheet" href="http://example.org/wp-content/themes/example-theme/style.css" />

在主题的 header.php 中可以发现对应的代码:

<link rel="stylesheet" href="<?php bloginfo( 'stylesheet_url' ); ?>" />

那么直接把它改成这样就好了:

<link rel="stylesheet" href="//example.org/wp-content/themes/example-theme/style.css" />

没错,就是这样,把 URI 由动态的 PHP 函数换成静态的协议级相对链接。当然,由于 WordPress 的主题千变万化,可能还有其他的资源如 JavaScript 甚至糟糕的 swf 等,要根据不同主题的实际情况进行修改。

除了主题自带的资源,还有一些插件可能会引用一些 JavaScript 什么的,这也需要在插件的文件中进行修改。大致思路也是根据 HTML 源代码,去找 PHP 代码,然后把里面的函数换成带有实际地址的“协议级相对链接”。

改完了 CSS、JavaScript 等“高危内容”之后,Chrome/Chromium 的协议图标会从 变成 ,不过,依然是有提示“混合内容”的标识,这主要是页面中的一些图片元素还是使用的 http:// 的 URI。如果你的目标是让它变成 的话,那就必须要解决这个问题。要解决这个问题,比解决 JavaScript 和 CSS 要复杂一些。插件中的图片什么的,可以通过把插件中的函数改掉,使其变成“协议级相对路径”来解决。但是已经发表的文章中的图片,就只能一篇篇改了。文章中图片不多的话,只需一篇篇编辑,然后在图片的属性中把 http://example.org/wp-content/uploads/path/to/file.png 这样的换成 //example.org/wp-content/uploads/path/to/file.png 就好了。如果文章比较多,这样一篇篇换显然是十分费神的,那就最好到数据库中批量查找替换了,这里不再赘述。

强制跳转?选择性跳转?

如果顺利完成了上面的步骤,那么现在你的 WordPress 是既可通过 http:// 访问,也可通过 https:// 访问,但是,如果希望 http:// 的访问跳转到 https:// 呢?这就需要用到 mod_rewrite 了。由于这篇文章是针对 Apache 用户的,所以 nginx 的规则就不提了。

首先要确认 Apache 开启了 mod_rewrite(默认安装是不开的),如果没有开启的话,需要使用 sudo a2enmod rewrite 来开启并 sudo service apache2 restart 来重启 Apache。

如果有服务器的完全权限的话,可以在服务器的配置文件中加入以下代码,如果不能编辑配置文件的话,那在站点的目录中添加 .htaccess 文件加入以下代码也行。当然,站点配置中要设置 AllowOverride,才能使站点目录中的 .htaccess 起作用:(请把 wzyboy.im 换成你自己的 WordPress 域名)

这段代码的意思是说:当检测到 HTTPS 未开启(!on 嘛)的时候,把请求重定向到 https:// 的对应 URI。Apache 的 mod_rewrite 是个很强悍且较通俗易懂的东西,完整的文档请看这里。如果是在站点配置文件中加入了这些代码,需要用 sudo service apache2 reload 重新载入配置文件才能生效。

现在再访问 http:// 的 WordPress 的话,全部会自动跳转到 https:// 版本的了。但是这样做有两个小问题:

  1. 目前搜索引擎中似乎只有 Google 能抓取 https:// 站点,而其他的搜索引擎都不抓取。
  2. IE 是不支持 SNI 的(XP + IE8 及以下),因此对于这些用户来说,显示的依然是“证书错误”(其实是没有错误,是 IE 自己有错误)。

对应的解决方法自然也是有的,比如这样:(这段代码参考了 LvWind 的博客。请注意把 wzyboy.im 换成自己的域名)

这段代码比刚刚那段多加了一个判断条件,即判断访客的 User Agent 是否是搜索引擎或者 IE 用户,如果不是,那才跳转到 https:// 版的页面。注意,这里面是不包含 Google Bot(Google 的爬虫)的,在因为 Google 本身就是能抓取 https:// 站点的,没必要重定向。而 IE 用户则是像其他的非 Google 的搜索引擎一般看待了,保持不跳转。

另外,使用 FeedBurner 的用户也需注意,FeedBurner 是不支持 SSL 的,所以上面的 UA 判断中也有 FeedBurner 的身影。

六、申请 StartSSL 免费证书

虽然自签名证书和第三方靠谱 CA 签名的证书都能起到加密的作用,但是自签名的最多自己用用,而 WordPress 上用自签名证书就不太好了。这就需要第三方 CA 签名的。但是大部分靠谱 CA 签名的证书都是收费的,而且价格不是很低。好在,还是有人想打破这个垄断的,CACert.org 和 StartSSL 就是两家提供免费 SSL/TLS 证书签名的 CA。前者是开源社区弄的,所以不少 Linux 发行版都承认这个证书,但是在 Windows 里什么的就比较悲剧,比如一台 Windows 7 的电脑访问 https://cacert.org/ 就会提示证书错误,但是一台 Ubuntu 12.04 LTS 的电脑就没有问题。

而 StartSSL 受到承认的范围就稍大一些了,现在主流的操作系统和浏览器中,只有 IE6 不支持,所以比较适合我等穷人来部署 HTTPS。

StartSSL 是以色列公司 StartCom 旗下的 CA。申请免费的证书并不复杂,甚至比上面介绍的自己生成还要简单,因为它甚至提供了在线生成私钥的功能,连自己生成私钥都免了。

至于 StartSSL 的申请流程么,网上似乎是有很多的,但是不知是不是年代久远的关系,都用要审核的一条路子,太麻烦,以下是免审核申请 StartSSL 免费证书的方法。

使用 Chrome/Chromium 或者 Firefox(其他浏览器可能会有问题)打开 StartSSL Control Panel,这里有三个选项,分别是 Authenticate、Sign up 和 Express Lane。第一个是登录认证用的,第二个是注册账号,第三个是“捷径”。如果选择 Sign up 的话是要人工审核的(可能会被拒绝),而如果你只是想申请一个免费的 SSL/TLS 证书的话,直接点 Express Lane 就好了。点进去之后会让你填一张个人信息表,这个要真实填写。完成之后就会提示你安装一个证书,注意,这个证书是 S/MIME 证书,而不是我们要申请的 SSL/TLS 证书。这个证书是安装到浏览器里的,是用户登录 StartSSL 网站用的,只是一个登录网站时的凭据,和最终我们要用在服务器上的证书完全无关。该网站不是用传统的用户名 + 密码的方式登录的,而是用这个证书进行登录,如果丢了这个证书的话,就相当于把用户名密码忘了,得要重新申请才行。StartSSL 会提示你备份这个证书的,从浏览器中导出就可以了。如果要在别的电脑上或是别的浏览器里登录这个 StartSSL 网站的话,需要把这个导出的证书复制到那个电脑上才行。

接下来是登录,登录的时候会提示选择证书:

Apache + WordPress + SSL 完全指南

当然是确认了,然后就可以登录到 StartSSL 的控制面板了。

首先需要在 Verification Wizard 中选择 Domain Name Verification 添加、验证域名,证明这个域名是属于你的。填写域名的时候只能填顶级域名,二级域名是不被支持的,但是证明顶级域名归你所有之后,你可以随意添加二级域名。验证的过程也很简单:StartSSL 会检查这个域名的 Whois 信息,读取其中填写的所有人邮箱,然后外加 webmaster、postmaster、hostmater 等常见的域名邮箱的收件人,让你任选一个邮箱,发送验证码。哪个邮箱能收到邮件就选择哪个喽:

Apache + WordPress + SSL 完全指南

收到邮件之后,把里面的验证码复制粘贴进去,就验证成功了,StartSSL 就知道这个域名是属于你的了。注意,这个域名验证的有效期是三十天,三十天后要重新验证,但是三十天之内不需要再次证明这个域名归你。接下来就是去选择 Certificate Wizard 开始准备生成证书!在下拉列表中,选择你需要生成的证书类型,当然是 Web Server SSL/TLS Certificate 了。接下来是生成私钥:

Apache + WordPress + SSL 完全指南

这个是在线生成了,你也可以自己用 OpenSSL 生成。不过用它的也一样。这里需要填一个临时密码,输入一个 10 字符以上,32 字符以下的密码即可,这个密码临时使用,十分钟后就可以忘掉了。至于下面的加密方式和强度什么的都可以保持默认。

下一步是保存这个私钥:

Apache + WordPress + SSL 完全指南

把图中文本框中的东西全部复制下来,然后保存成一个纯文本文件,比如 ssl.key 。由于牵扯到 Windows/DOS 和 Linux/Unix 的换行符问题,建议还是在 Linux 环境下操作,比如在 SSH Shell 用 Vim 来保存这样一个文件。保存好之后把它解密:openssl rsa -in ssl.key -out ssl.key,输入刚才设置的那个临时密码即可解密。之后临时密码就可以忘掉了。如果没有 Linux 环境的话,也可以用它提供的在线解密工具(在 Tool Box 里)。

接下来是添加子域名:

Apache + WordPress + SSL 完全指南

图中填的域名将会被作为最终的 Common Name,而裸域则是作为 Alternative Name 出现的。

再次确认之后,生成证书:

Apache + WordPress + SSL 完全指南

把图中文本框里的内容也保存成一个纯文本文件,比如 ssl.crt。(还是建议在主机上直接用 Vim 等保存,否则可能会有换和符问题)然后图中还有一个 intermediate 证书,那个也要下载保存一下,那个就是上文中所说的 ChainFile 了。至于那个 root 证书,由于大家的电脑里都有,所以就没必要下载。

现在你已经集齐了证书的三个要素了:ssl.crt 是证书信息和公钥,ssl.key 是证书的私钥,而那个 sub.class1.server.ca.pem 则是 ChainFile。把这三个文件放到服务器里的合理的目录下,再在 Apache 配置文件里指定它们,大功告成!

此外,StartSSL 还发给过你一个 S/MIME 证书,那个是你登录 StartSSL 网站的唯一凭据,把它弄丢了的话就无法登录 StartSSL 的网站了(只能重新申请了),所以也要备份好。Firefox 和 Chrome/Chromium 的证书备份都不复杂,在此不再赘述。

七、尾声

本文原载于 wzyboy's blog原文以 CC BY-NC-SA 3.0 释出,有修改。

补充1:经实验证明,FeedBurner 是不能识别 https:// 链接的,所以上文中判断 UA 的一节添加了 FeedBurner 的 UA。不使用 FeedBurner 的用户可以不加。