跳到内容

关于 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 地址。这将是你服务器使用的公共 IP 地址,也就是你在 DNS 服务器中配置的那个。

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 请求传输给运行应用程序的进程(例如,一个运行 FastAPI 应用程序的 Uvicorn 进程)。

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 终止代理。这并不理想,因为在 TLS 终止代理关闭期间,你的应用程序将不可用。

所有这些续期过程,同时还要为应用程序提供服务,是为什么你想要有一个独立的系统来处理 HTTPS(使用 TLS 终止代理),而不是直接在应用程序服务器(例如 Uvicorn)上使用 TLS 证书的主要原因之一。

代理转发的请求头

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

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

技术细节

这些代理头部是:

然而,由于应用程序服务器不知道它位于一个受信任的代理之后,默认情况下,它不会信任这些头部。

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

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

这样,应用程序就能知道自己的公共 URL、是否使用 HTTPS、域名等信息。

这对于例如正确处理重定向会很有用。

提示

你可以在 位于代理之后 - 启用代理转发的请求头 的文档中了解更多信息。

总结

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

但是一旦你了解了给开发者的 HTTPS 指南中的基本信息,你就可以轻松地组合和配置不同的工具,以简单的方式帮助你管理一切。

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