关于 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 响应¶
应用程序将处理请求,并将一个**明文(未加密)的 HTTP 响应**发送给 TLS 终止代理。
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 终止代理)而不是直接将 TLS 证书与应用程序服务器(例如 Uvicorn)一起使用的主要原因之一。
代理转发的头部¶
在使用代理处理 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**。🔒