跳到内容

关于 HTTPS

人们很容易认为 HTTPS 只是一个“启用”或“未启用”的状态。

但它实际上要复杂得多。

提示

如果你赶时间或者并不关心细节,请直接跳转到后续章节,获取使用不同技术配置 HTTPS 的分步指南。

若要从用户角度了解 HTTPS 的基础知识,请查看 https://howhttps.works/

现在,从开发者的角度来看,在考虑 HTTPS 时需要记住以下几点:

  • 对于 HTTPS,服务器需要拥有第三方颁发的“证书”。
    • 这些证书实际上是从第三方获取的,而不是“生成的”。
  • 证书具有有效期
    • 它们会过期
    • 到期后需要续期,即再次从第三方获取
  • 连接的加密发生在 TCP 层
    • 这比 HTTP 层低一层
    • 因此,证书和加密处理是在 HTTP 之前完成的。
  • TCP 不了解“域名”,它只识别 IP 地址。
    • 关于特定域名的请求信息包含在 HTTP 数据中。
  • HTTPS 证书用于“证明”特定域名,但协议和加密发生在 TCP 层,即在知道处理的是哪个域名之前。
  • 默认情况下,这意味着每个 IP 地址只能有一个 HTTPS 证书
    • 无论你的服务器有多大,或者上面运行的每个应用程序有多小,皆是如此。
    • 不过,有一个解决方案
  • TLS 协议(在 HTTP 之前处理 TCP 层加密的协议)有一个扩展,称为 SNI(服务器名称指示)。
    • 此 SNI 扩展允许单个服务器(拥有单个 IP 地址)拥有多个 HTTPS 证书,并服务于多个 HTTPS 域名/应用程序
    • 为了实现这一点,运行在服务器上并监听公共 IP 地址单个组件(程序)必须拥有服务器上所有的 HTTPS 证书
  • 在建立安全连接之后,通信协议仍然是 HTTP
    • 即使内容是通过 HTTP 协议发送的,它们也是加密的

通常的做法是在服务器(机器、主机等)上运行一个程序/HTTP 服务器管理所有 HTTPS 相关事务:接收加密的 HTTPS 请求,将解密后的 HTTP 请求发送到运行在同一服务器上的实际 HTTP 应用程序(本例中为 FastAPI 应用程序),获取应用程序的 HTTP 响应,使用相应的 HTTPS 证书对其加密,然后使用 HTTPS 将其发送回客户端。这个服务器通常被称为 TLS 终止代理

你可以用作 TLS 终止代理的一些选项包括:

  • Traefik(也可以处理证书续期)
  • Caddy(也可以处理证书续期)
  • Nginx
  • HAProxy

Let's Encrypt

在 Let's Encrypt 出现之前,这些 HTTPS 证书是由受信任的第三方出售的。

获取这些证书的过程曾经非常繁琐,需要大量的文书工作,而且证书非常昂贵。

后来,Let's Encrypt 诞生了。

这是 Linux 基金会的一个项目。它以自动化的方式免费提供 HTTPS 证书。这些证书使用所有标准的加密安全技术,且有效期较短(约 3 个月),因此由于其较短的生命周期,安全性实际上更高

域名经过安全验证并自动生成证书。这也实现了这些证书的自动化续期。

其理念是自动化获取和续期这些证书,这样你就可以永久免费地获得安全的 HTTPS

面向开发者的 HTTPS

这是一个 HTTPS API 如何运作的逐步示例,重点关注对开发者重要的概念。

域名

这一切通常从你获取某个域名开始。然后,你会在 DNS 服务器(可能是你的云服务商)中进行配置。

你通常会购买一台云服务器(虚拟机)或类似的设备,它会有一个 固定公共 IP 地址

在 DNS 服务器中,你将配置一条记录(“A 记录”),将你的域名指向你服务器的公共 IP 地址

你通常只需在第一次设置时执行此操作。

提示

域名部分发生在 HTTPS 之前,但由于一切都依赖于域名和 IP 地址,因此在这里提一下很有必要。

DNS

现在让我们专注于所有实际的 HTTPS 部分。

首先,浏览器会向 DNS 服务器查询域名的 IP 地址,在本例中为 someapp.example.com

DNS 服务器会告诉浏览器使用特定的 IP 地址。这就是你在 DNS 服务器中配置的、你服务器所使用的公共 IP 地址。

TLS 握手开始

然后,浏览器会在 443 端口(HTTPS 端口)上与该 IP 地址进行通信。

通信的第一部分仅用于在客户端和服务器之间建立连接,并决定它们将使用的加密密钥等。

客户端和服务器之间为建立 TLS 连接而进行的交互被称为 TLS 握手

带有 SNI 扩展的 TLS

服务器上的只有一个进程可以在特定 IP 地址的特定 端口上进行监听。同一个 IP 地址上的其他端口可以有其他进程在监听,但对于 IP 地址和端口的每个组合,只能有一个进程。

TLS (HTTPS) 默认使用特定的 443 端口。所以这就是我们需要使用的端口。

由于只能有一个进程监听此端口,因此该进程必须是 TLS 终止代理

TLS 终止代理可以访问一个或多个 TLS 证书(HTTPS 证书)。

通过使用上述的 SNI 扩展,TLS 终止代理会根据客户端期望的域名,检查应该使用哪个可用的 TLS (HTTPS) 证书。

在本例中,它将使用 someapp.example.com 的证书。

客户端已经信任生成该 TLS 证书的实体(在本例中是 Let's Encrypt),因此它可以验证证书是否有效。

然后,客户端和 TLS 终止代理利用该证书决定如何加密其余的 TCP 通信。这完成了 TLS 握手部分。

在此之后,客户端和服务器就拥有了一个加密的 TCP 连接,这就是 TLS 提供的功能。随后,它们可以使用该连接开始实际的 HTTP 通信

这就是 HTTPS,它只是在安全的 TLS 连接内传输的纯 HTTP,而不是纯粹的(未加密的)TCP 连接。

提示

请注意,通信的加密发生在 TCP 层,而不是 HTTP 层。

HTTPS 请求

既然客户端和服务器(具体来说是浏览器和 TLS 终止代理)已经拥有了加密的 TCP 连接,它们就可以开始 HTTP 通信了。

因此,客户端发送 HTTPS 请求。这只是通过加密的 TLS 连接传输的 HTTP 请求。

解密请求

TLS 终止代理将使用约定的加密方式解密请求,并将纯(解密后的)HTTP 请求发送给运行应用程序的进程(例如使用 Uvicorn 运行的 FastAPI 应用程序进程)。

HTTP 响应

应用程序处理该请求并向 TLS 终止代理发送纯(未加密的)HTTP 响应

HTTPS 响应

然后,TLS 终止代理会使用之前约定的加密方式(以 someapp.example.com 的证书开始)加密响应,并将其发送回浏览器。

接下来,浏览器会验证响应是否有效且使用了正确的加密密钥加密,等等。然后它会解密响应并处理它。

客户端(浏览器)将知道响应来自正确的服务器,因为它使用了之前通过 HTTPS 证书商定的加密方式。

多个应用程序

在同一台(或多台)服务器上,可以运行多个应用程序,例如其他 API 程序或数据库。

虽然只能有一个进程处理特定的 IP 和端口(在我们的例子中是 TLS 终止代理),但其他应用程序/进程也可以运行在服务器上,只要它们不尝试使用相同的公共 IP 和端口组合即可。

通过这种方式,TLS 终止代理可以为多个域名和多个应用程序处理 HTTPS 和证书,然后将请求转发给各自正确的应用程序。

证书续期

在未来的某个时间点,每个证书都会过期(获取后约 3 个月)。

届时,会有另一个程序(有时是另一个程序,有时可能是同一个 TLS 终止代理)与 Let's Encrypt 通信并续期证书。

TLS 证书域名相关联,而不是与 IP 地址相关联。

因此,为了续期证书,续期程序需要向权威机构(Let's Encrypt)证明它确实“拥有”并控制该域名

为了做到这一点,并适应不同的应用需求,有几种实现方式。一些流行的方式包括:

  • 修改某些 DNS 记录.
    • 为此,续期程序需要支持 DNS 提供商的 API,因此,根据你所使用的 DNS 提供商,这可能是一个可行选项,也可能不是。
  • 作为服务器运行(至少在证书获取过程中),绑定到与域名关联的公共 IP 地址上。
    • 正如我们上面所说的,一个特定的 IP 和端口上只能有一个进程在监听。
    • 这也是为什么由同一个 TLS 终止代理同时处理证书续期非常实用的原因之一。
    • 否则,你可能需要暂时停止 TLS 终止代理,启动续期程序以获取证书,配置好后再重启 TLS 终止代理。这并不理想,因为在 TLS 终止代理关闭期间,你的应用程序将无法访问。

在保持应用服务的同时进行整个续期过程,这是你希望拥有独立的 HTTPS 处理系统(通过 TLS 终止代理),而不是直接在应用服务器(例如 Uvicorn)中使用 TLS 证书的主要原因之一。

代理转发头 (Proxy Forwarded Headers)

当使用代理处理 HTTPS 时,你的应用程序服务器(例如通过 FastAPI CLI 使用的 Uvicorn)对 HTTPS 过程一无所知,它通过纯 HTTP 与 TLS 终止代理通信。

代理通常会在将请求传输到应用程序服务器之前,动态设置一些 HTTP 头信息,以告知应用程序服务器请求是由代理转发的。

技术细节

代理头包括:

尽管如此,由于应用程序服务器不知道它处于受信任的代理之后,默认情况下,它不会信任这些头信息。

但你可以配置应用程序服务器以信任代理发送的转发头。如果你使用的是 FastAPI CLI,可以使用 CLI 选项 --forwarded-allow-ips 来指定它应该信任哪些 IP 发送的转发头。

例如,如果应用程序服务器只接收来自受信任代理的通信,你可以将其设置为 --forwarded-allow-ips="*",使其信任所有传入的 IP,因为它只会接收来自代理所使用 IP 的请求。

这样,应用程序就能知道它自己的公共 URL 是什么,是否正在使用 HTTPS,域名是什么,等等。

这对于正确处理重定向等操作非常有用。

提示

你可以从 代理之后 - 启用代理转发头 文档中了解更多相关信息。

回顾

拥有 HTTPS 非常重要,在大多数情况下甚至至关重要。作为开发者,你在 HTTPS 上所投入的大部分努力都只是为了理解这些概念以及它们是如何工作的。

但一旦你了解了面向开发者的 HTTPS 的基础知识,你就可以轻松组合和配置各种工具,以简单的方式管理一切。

在接下来的章节中,我将向你展示几个具体的示例,演示如何为 FastAPI 应用程序设置 HTTPS。 🔒