你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

保留反向代理与其后端 Web 应用程序之间的原始 HTTP 主机名

Azure API 管理
Azure 应用服务
Azure 应用程序网关
Azure Front Door
Azure Spring Apps

建议在 Web 应用程序前面使用反向代理时保留原始 HTTP 主机名。 在反向代理中具有不同于提供给后端应用程序服务器的主机名可能会导致 Cookie 或重定向 URL 无法正常工作。 例如,会话状态可能会丢失,身份验证可能失败,或者后端 URL 可能会无意中向最终用户公开。 可以通过保留初始请求的主机名来避免这些问题,以便应用程序服务器看到与 Web 浏览器相同的域。

本指南尤其适用于在平台即服务(PaaS)中托管的应用程序,例如 Azure 应用服务Azure Spring Apps。 本文提供有关 Azure 应用程序网关Azure Front DoorAzure API 管理(通常用于反向代理服务)的特定 实现 指南。

注意

Web API 通常对主机名不匹配导致的问题不太敏感。 它们通常不依赖于 Cookie,除非你 使用 Cookie 来保护单页应用与其后端 API之间的通信,例如,在称为前端 后端的模式中。 除了某些 API 样式(如 开放数据协议(OData)HATEOAS,Web API 通常不会返回回自己的绝对 URL。 如果 API 实现依赖于 Cookie 或生成绝对 URL,本文中提供的指南将适用。

如果需要端到端 TLS/SSL(反向代理与后端服务之间的连接使用 HTTPS),后端服务还需要与原始主机名匹配的 TLS 证书。 部署和续订证书时,此要求会增加操作复杂性,但许多 PaaS 服务提供免费的 TLS 证书,这些证书完全托管。

上下文

HTTP 请求的主机

在许多情况下,请求管道中的应用程序服务器或某些组件需要浏览器用来访问它的 Internet 域名。 这是请求 主机。 它可以是一个 IP 地址,但通常是一个名称,如 contoso.com(浏览器随后使用 DNS 解析为 IP 地址)。 主机值通常从请求 URI主机组件确定,浏览器会将请求 URI作为 HTTP 标头发送到应用程序。

重要

切勿在安全机制中使用主机的值。 该值由浏览器或其他一些用户代理提供,可由最终用户轻松操作。

在某些情况下,尤其是在请求链中存在 HTTP 反向代理时,原始主机标头可以在到达应用程序服务器之前更改。 反向代理关闭客户端网络会话,并设置与后端的新连接。 在此新会话中,它可以传递客户端会话的原始主机名或设置新主机名。 在后一种情况下,代理通常会在其他 HTTP 标头(如 ForwardedX-Forwarded-Host)中发送原始主机值。 此值允许应用程序确定原始主机名,但前提是它们被编码为读取这些标头。

为什么 Web 平台使用主机名

多租户 PaaS 服务通常需要注册和验证的主机名,以便将传入请求路由到相应租户的后端服务器。 这是因为通常有一个共享的负载均衡器池,用于接受所有租户的传入请求。 租户通常使用传入主机名来查找客户租户的正确后端。

为了便于入门,这些平台通常提供一个预配置的默认域,用于将流量路由到已部署的实例。 对于应用服务,此默认域 azurewebsites.net。 创建的每个 Web 应用都会获取其自己的子域,例如 contoso.azurewebsites.net。 同样,默认域是 Azure Spring Apps 的 azuremicroservices.ioazure-api.net 用于 API 管理。

对于生产部署,不使用这些默认域。 而是提供自己的域,使其与组织或应用程序的品牌保持一致。 例如,contoso.com 可以在后台解析为应用服务上的 contoso.azurewebsites.net Web 应用,但访问网站的最终用户不应看到此域。 但是,此自定义 contoso.com 主机名必须注册到 PaaS 服务,因此平台可以标识应响应请求的后端服务器。

关系图,说明应用服务中基于主机的路由。

应用程序为何使用主机名

应用程序服务器需要主机名的两个常见原因是构造绝对 URL 并为特定域颁发 Cookie。 例如,当应用程序代码需要:

  • 在其 HTTP 响应中返回绝对 URL 而不是相对 URL(尽管网站通常会尽可能呈现相对链接)。
  • 生成在 HTTP 响应之外使用的 URL,其中无法使用相对 URL,例如将指向网站的链接通过电子邮件发送给用户。
  • 为外部服务生成绝对重定向 URL。 例如,对于身份验证服务(如 Microsoft Entra ID),以指示在身份验证成功后应返回用户的位置。
  • 发出限制为特定主机的 HTTP Cookie,如 cookie 的 Domain 属性中定义。

可以通过将预期的主机名添加到应用程序的配置中并使用静态定义的值而不是请求中的传入主机名来满足所有这些要求。 但是,此方法使应用程序开发和部署复杂化。 此外,应用程序的单个安装可以为多个主机提供服务。 例如,单个 Web 应用可用于具有其自己的唯一主机名(如 tenant1.contoso.comtenant2.contoso.com)的多个应用程序租户。

有时,传入的主机名由应用程序代码外部的组件或你没有完全控制的应用程序服务器上的中间件使用。 下面是一些示例:

  • 在应用服务中,可以 为 Web 应用强制实施 HTTPS。 这样做会导致任何不安全的 HTTP 请求重定向到 HTTPS。 在这种情况下,传入主机名用于生成 HTTP 重定向的 Location 标头的绝对 URL。
  • Azure Spring Apps 使用类似的功能来 强制实施 HTTPS。 它还使用传入主机生成 HTTPS URL。
  • 应用服务具有 ARR 关联设置, 启用粘滞会话,以便来自同一浏览器实例的请求始终由同一后端服务器提供服务。 这由应用服务前端执行,该前端将 Cookie 添加到 HTTP 响应。 cookie 的 Domain 设置为传入主机。
  • 应用服务提供 身份验证和授权功能,以便用户轻松登录和访问 API 中的数据。

为什么你可能被诱惑重写主机名

假设在应用服务中创建具有默认域 contoso.azurewebsites.net的 Web 应用程序。 (或在 Azure Spring Apps 等其他服务中)。尚未在应用服务上配置自定义域。 若要在此应用程序前面放置反向代理(或任何类似的服务),请为 contoso.com 设置 DNS 记录以解析为应用程序网关的 IP 地址。 因此,它从浏览器接收 contoso.com 请求,并配置为将请求转发到 contoso.azurewebsites.net 解析为的 IP 地址:这是所请求主机的最终后端服务。 但是,在这种情况下,应用服务无法识别 contoso.com 自定义域,并拒绝此主机名的所有传入请求。 它无法确定路由请求的位置。

进行此配置工作的简单方法是重写或重写应用程序网关中 HTTP 请求的 Host 标头,并将其设置为 contoso.azurewebsites.net的值。 如果这样做,来自应用程序网关的传出请求似乎确实适用于 contoso.azurewebsites.net 而不是 contoso.com

关系图,该图演示了重写了主机名的配置。

此时,应用服务会识别主机名,并且它接受请求,而无需配置自定义域名。 事实上,应用程序网关可以轻松地使用后端池的主机替代主机标头Azure Front Door 在默认情况下也是如此。

但是,此解决方案的问题在于,当应用看不到原始主机名时,它可能会导致各种问题。

潜在问题

不正确的绝对 URL

如果未保留原始主机名,并且应用程序服务器使用传入主机名生成绝对 URL,则后端域可能会向最终用户披露。 这些绝对 URL 可以由应用程序代码生成,或者如前所述,由平台功能(如支持应用服务和 Azure Spring Apps 中的 HTTP 到 HTTPS 重定向)生成。 此图说明了问题:

关系图,说明错误的绝对 URL 问题。

  1. 浏览器向反向代理发送 contoso.com 请求。
  2. 反向代理将主机名重写为后端 Web 应用程序的请求中的 contoso.azurewebsites.net(或另一个服务的类似默认域)。
  3. 应用程序生成一个绝对 URL,该 URL 基于传入的 contoso.azurewebsites.net 主机名,例如 https://contoso.azurewebsites.net/
  4. 浏览器遵循此 URL,该 URL 直接转到后端服务,而不是返回到 contoso.com处的反向代理。

在反向代理也充当 Web 应用程序防火墙的常见情况下,这甚至可能构成安全风险。 用户会收到一个直接转到后端应用程序的 URL,并绕过反向代理。

重要

由于这种安全风险,需要确保后端 Web 应用程序仅直接接受来自反向代理的网络流量(例如,通过在应用服务中使用 访问限制)。 如果这样做,即使生成了不正确的绝对 URL,它至少不起作用,并且恶意用户无法使用它来绕过防火墙。

重定向 URL 不正确

生成绝对重定向 URL 时,会出现上一个场景的常见和更具体的情况。 使用基于浏览器的标识协议(如 OpenID Connect、Open Authorization (OAuth) 2.0 或安全断言标记语言 (SAML) 2.0 时,标识服务(如 Microsoft Entra ID)需要这些 URL。 这些重定向 URL 可由应用程序服务器或中间件本身生成,或者如前所述,由应用服务等平台功能 身份验证和授权功能生成。 此图说明了问题:

图,说明重定向 URL 不正确的问题。

  1. 浏览器向反向代理发送 contoso.com 请求。
  2. 反向代理将主机名重写为对后端 Web 应用程序的请求 contoso.azurewebsites.net(或另一个服务的类似默认域)。
  3. 应用程序生成一个基于传入 contoso.azurewebsites.net 主机名的绝对重定向 URL,例如 https://contoso.azurewebsites.net/
  4. 浏览器转到标识提供者以对用户进行身份验证。 请求包括生成的重定向 URL,用于指示身份验证成功后返回用户的位置。
  5. 标识提供者通常需要提前注册重定向 URL,因此此时标识提供者应拒绝请求,因为未注册提供的重定向 URL。 (它不应该被使用。但是,如果出于某种原因注册了重定向 URL,则标识提供者会将浏览器重定向到身份验证请求中指定的重定向 URL。 在这种情况下,URL https://contoso.azurewebsites.net/
  6. 浏览器遵循此 URL,该 URL 直接转到后端服务,而不是返回到反向代理。

损坏的 Cookie

当应用程序服务器发出 Cookie 并使用传入主机名构造 cookie的 属性时,主机名不匹配也可能导致问题。 域属性可确保 Cookie 仅用于该特定域。 这些 Cookie 可由应用程序代码生成,或者如前所述,由平台功能(如应用服务 ARR 关联设置)生成。 此图说明了问题:

说明 Cookie 域不正确的关系图。

  1. 浏览器向反向代理发送 contoso.com 请求。
  2. 反向代理重写主机名,以便在后端 Web 应用程序的请求中 contoso.azurewebsites.net(或另一个服务的类似默认域)。
  3. 应用程序生成基于传入 contoso.azurewebsites.net 主机名使用域的 Cookie。 浏览器存储此特定域的 cookie,而不是用户实际使用的 contoso.com 域。
  4. 浏览器不会在任何后续请求中包含 cookie contoso.com,因为 cookie 的 contoso.azurewebsites.net 域与请求的域不匹配。 应用程序不会收到之前颁发的 Cookie。 因此,用户可能会丢失应位于 Cookie 中的状态,或者 ARR 相关性等功能不起作用。 遗憾的是,这些问题都不会产生错误或直接对最终用户可见。 这使得他们难以进行故障排除。

常见 Azure 服务的实现指南

为了避免此处讨论的潜在问题,我们建议在反向代理和后端应用程序服务器之间的调用中保留原始主机名:

关系图,其中显示了保留主机名的配置。

后端配置

许多 Web 托管平台要求显式配置允许的传入主机名。 以下部分介绍如何为最常见的 Azure 服务实现此配置。 其他平台通常提供用于配置自定义域的类似方法。

如果在 应用服务中托管 Web 应用程序,则可以 将自定义域名附加到 Web 应用,并避免在后端使用默认 azurewebsites.net 主机名。 将自定义域附加到 Web 应用时,无需更改 DNS 解析:可以使用 记录 来 验证域,而不会影响常规 记录或 记录。 (这些记录仍将解析为反向代理的 IP 地址。如果需要端到端 TLS/SSL,则可以 从 Key Vault 导入现有证书,或者为自定义域使用 应用服务证书。 (请注意,在这种情况下无法使用免费 应用服务托管证书,因为它要求域的 DNS 记录直接解析为应用服务,而不是反向代理。

同样,如果使用 Spring Apps,则可以 为应用使用自定义域 以避免使用 azuremicroservices.io 主机名。 如果需要端到端 TLS/SSL,可以导入现有证书或自签名证书。

如果在 API 管理(它本身也充当反向代理)前面有反向代理,则可以 在 API 管理实例上配置自定义域 以避免使用 azure-api.net 主机名。 如果需要端到端 TLS/SSL,可以导入现有的或免费的托管证书。 不过,如前所述,API 对主机名不匹配导致的问题不太敏感,因此此配置可能不如重要。

如果在 其他平台(如 Kubernetes 上或直接在虚拟机上)托管应用程序,则没有依赖于传入主机名的内置功能。 你负责如何在应用程序服务器本身中使用主机名。 保留主机名的建议通常仍适用于依赖于它的应用程序中的任何组件,除非你专门让应用程序知道反向代理并遵守 forwardedX-Forwarded-Host 标头,例如。

反向代理配置

在反向代理中定义后端时,仍可以使用后端服务的默认域,例如,https://contoso.azurewebsites.net/。 反向代理使用此 URL 解析后端服务的正确 IP 地址。 如果使用平台的默认域,始终保证 IP 地址正确。 通常无法使用面向公众的域(如 contoso.com),因为它应解析为反向代理本身的 IP 地址。 (除非使用更高级的 DNS 解析技术,如 拆分范围 DNS)。

重要

如果具有下一代防火墙(如 Azure 防火墙高级版 反向代理和最终后端之间,则可能需要使用拆分范围 DNS。 这种类型的防火墙可能会显式检查 HTTP Host 标头是否解析为目标 IP 地址。 在这些情况下,浏览器使用的原始主机名应在从公共 Internet 访问反向代理时解析为反向代理的 IP 地址。 但是,从防火墙的角度来看,该主机名应解析为最终后端服务的 IP 地址。 有关详细信息,请参阅使用 Azure 防火墙和应用程序网关为 Web 应用程序 零信任网络。

大多数反向代理都允许配置将哪个主机名传递给后端服务。 以下信息说明了如何确保使用传入请求的原始主机名(对于最常见的 Azure 服务)。

注意

在所有情况下,还可以选择使用显式定义的自定义域替代主机名,而不是从传入请求中获取主机名。 如果应用程序仅使用单个域,则此方法可能正常工作。 如果同一应用程序部署接受来自多个域的请求(例如,在多租户方案中),则无法静态定义单个域。 应从传入请求中获取主机名(同样,除非显式编码应用程序以考虑其他 HTTP 标头)。 因此,一般建议是根本不应重写主机名。 将传入的主机名未修改传递到后端。

应用程序网关

如果使用 应用程序网关 作为反向代理,则可以通过在后端 HTTP 设置上禁用 替代 来确保保留原始主机名。 这样做会禁用 从后端地址 选取主机名,并使用特定域名替代。 (这两个设置都覆盖主机名。在应用程序网关Azure 资源管理器属性中,此配置对应于将 属性设置为 设置为

由于运行状况探测在传入请求的上下文之外发送,因此无法动态确定正确的主机名。 相反,必须创建自定义运行状况探测,禁用 从后端 HTTP 设置选取主机名,并 显式指定主机名。 对于此主机名,还应使用适当的自定义域来保持一致性。 (但是,可以在此处使用托管平台的默认域,因为运行状况探测会忽略响应中不正确的 Cookie 或重定向 URL。

Azure Front Door

如果使用 Azure Front Door,可以通过在源定义中保留 源主机标头 空白来保留主机名。 在源的 资源管理器定义中,此配置对应于将 设置为

API 管理

默认情况下,API 管理 会用 API 的 Web 服务 URL 的主机组件(对应于 API资源管理器定义的 值)替代发送到后端的主机名。

可以通过添加 inboundSet 标头 策略来强制 API 管理改用传入请求的主机名,如下所示:

<inbound>
  <base />
  <set-header name="Host" exists-action="override">
    <value>@(context.Request.OriginalUrl.Host)</value>
  </set-header>
</inbound>

不过,如前所述,API 对主机名不匹配导致的问题不太敏感,因此此配置可能不如重要。

后续步骤

  • 使用 Azure 防火墙和应用程序网关为 Web 应用程序 零信任网络
  • 使用应用程序网关和 API 管理 保护 API
  • 使用应用服务环境 企业部署