Heroku + Node.js + HTTPS

Cat Chen 2018-07-02 05:48

昨天把biz-to-me升级到支持 HTTPS 了,为此研究了一下如何让 Heroku 上跑的 Node.js 应用支持 HTTPS。我发现并没有任何文章描述这个具体的流程,只有零碎的信息,所以在此记录一下。

首先,Heroku 应用要支持 HTTPS 必须要是付费的等级,最便宜的是每月 $7 的 Hobby 级别。把应用升级到 Hobby 级别后,我们在应用设置里面添加的域名就能自动获得 SSL 证书,这个过程是全自动的,无需手动操作。(全自动不意味着实时,每次添加新域名都需要等一段时间才会看到「ACM Status」这一栏变成「Ok」的状态。背后实际使用的证书签发机构其实是Let’s Encrypt。)

Banners_and_Alerts_and_biz-to-me_·_Settings___Heroku

一般 Heroku 应用的「DNS Target」是app-name.herokuapp.com,需要留意的是一旦升级到支持 SSL 后这一栏的内容会改变,变成app.example.com.herokudns.com,也就是在herokudns.com之前加上应用的整个域名。如果之前 DNS 的 CNAME 指向还是herokuapp.com,那必须记得更新为herokudns.com,否则herokuapp.com不会使用正确的 SSL 证书提供服务。(herokuapp.com永远只会用*.herokuapp.com的证书提供服务,而herokudns.com会根据前缀选择正确的证书。)

在大多数情况下,Heroku 应用升级到支持 HTTPS 后应用代码不需要更新,这是因为 SSL 连接在 Heroku 的负载均衡器那里就种终止了,负载均衡器用明文 HTTP 跟应用连接所以应用本身不需要具备处理 HTTPS 的能力。(Node.js 有https模块,但调用时需要访问证书私钥,所以还是用http模块比较方便。)如果需要在 Node.js 中判断上游请求是不是 HTTPS,可以通过读取X-Forwarded-Protoheader 来实现,这个 header 的取值可以是"http"或"https",例如说在 biz-to-me 中我就通过这一行代码来进行判断。

最后简述一下如何验证配置是否成功。最简单的方法自然是测试一下 HTTPS 服务是否正常工作。biz-to-me 的服务很简单,如果我打开一个 http(s)://*.catchen.biz/* 的 URL,它返回 301 把我重定向到 http(s)://*.catchen.me/*。这篇文章的 URL 是https://chinese.catchen.me/2018/07/heroku-nodejs-https.html,如果我curl -i对应的 catchen.biz URL 能得到正确的 301 重定向那就是成功了。

$ curl -i https://chinese.catchen.biz/2018/07/heroku-nodejs-https.htmlHTTP/1.1 301 Moved PermanentlyServer: CowboyConnection: keep-aliveLocation: https://chinese.catchen.me/2018/07/heroku-nodejs-https.htmlDate: Sun, 01 Jul 2018 21:29:36 GMTTransfer-Encoding: chunkedVia: 1.1 vegurPermanently moved to <a href="https://chinese.catchen.me/2018/07/heroku-nodejs-https.html">https://chinese.catchen.me/2018/07/heroku-nodejs-https.html</a>.

留意Locationheader 的值是 HTTPS 开头的而非 HTTP,因为在 Node.js 代码更新到支持 HTTPS 之前我们也能得到类似的 301 响应,只不过Location永远都只是 HTTP 的。

如果curl -i因为证书不正确而出错的话,可以用curl --insecure来忽略证书验证 Node.js 代码。然后再用curl -v来查看证书,看看到底为什么证书错误。curl -v的结果关键看这一段:

* Server certificate:*  subject: CN=cantonese.catchen.biz*  start date: Jul  1 04:44:37 2018 GMT*  expire date: Sep 29 04:44:37 2018 GMT*  subjectAltName: host "chinese.catchen.biz" matched cert's "chinese.catchen.biz"*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3*  SSL certificate verify ok.

如果 Heroku 应用只添加了一个域名,subject那一行的域名必须是这个唯一域名。如果看到subject那一行显示的是*.herokuapp.com,那意味着我们需要把域名 CNAME 改为herokudns.com,或者是域名变更还没生效。(变更还没在本地 DNS 生效的话,可以用curl --resolve在本地强行覆盖。)如果 Heroku 应用有多个域名,subject会是其中一个,但subjectAltName会有多个域名,至少应该找到有一个对的。

最后,如果你喜欢我的文章的话,欢迎通过邮件RSS/Atom订阅我的博客。

[返回] [原文链接]