关于 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 请求**传输到运行应用程序的进程(例如运行 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 终止代理,而不是直接与应用程序服务器(例如 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** 的具体示例。🔒