跳转至

关于 HTTPS

人们很容易认为 HTTPS 只是一种“启用”或“不启用”的功能。

但实际上它远比这复杂。

Tip

如果您赶时间或不在意,请继续阅读后续章节,其中提供了使用不同技术逐步设置所有内容的说明。

从消费者角度了解 HTTPS 的基础知识,请查看 https://howhttps.works/

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

  • 对于 HTTPS,服务器需要拥有由第三方生成的“证书”。
    • 这些证书实际上是从第三方获取的,而不是“生成”的。
  • 证书有生命周期
    • 它们会过期
    • 然后需要续订,从第三方重新获取
  • 连接的加密发生在 TCP 层级
    • 这比 HTTP 低一层
    • 因此,证书和加密处理在 HTTP 之前完成。
  • TCP 不知道“域名”。它只知道 IP 地址。
    • 关于所请求特定域名的信息放在 HTTP 数据中。
  • HTTPS 证书“认证”一个特定域名,但协议和加密发生在 TCP 层级,在知道要处理哪个域名之前。
  • 默认情况下,这意味着每个 IP 地址只能有一个 HTTPS 证书
    • 无论您的服务器有多大,或者您在上面运行的每个应用程序有多小。
    • 不过,对此有一个解决方案
  • TLS 协议(在 TCP 层级处理加密,在 HTTP 之前)有一个扩展,称为 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 地址

您可能只在第一次设置所有内容时执行此操作。

Tip

这部分域名内容远在 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 连接。

Tip

请注意,通信的加密发生在 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,域名等。

例如,这对于正确处理重定向非常有用。

Tip

您可以在 Behind a Proxy - Enable Proxy Forwarded Headers 的文档中了解更多信息。

回顾

拥有 HTTPS 非常重要,并且在大多数情况下相当关键。您作为开发人员必须围绕 HTTPS 付出的大部分努力只是理解这些概念以及它们如何工作。

但是一旦您了解了面向开发者的 HTTPS 的基本信息,您就可以轻松地组合和配置不同的工具,以简单的方式帮助您管理所有内容。

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