跳到内容

如何配置 Symfony 以在负载均衡器或反向代理后工作

编辑此页

当您部署应用程序时,您可能位于负载均衡器(例如 AWS Elastic Load Balancing)或反向代理(例如用于 缓存 的 Varnish)之后。

在大多数情况下,这不会给 Symfony 带来任何问题。但是,当请求通过代理时,某些请求信息会使用标准 Forwarded 标头或 X-Forwarded-* 标头发送。例如,用户的真实 IP 将存储在标准 Forwarded: for="..." 标头或 X-Forwarded-For 标头中,而不是读取 REMOTE_ADDR 标头(现在将是您的反向代理的 IP 地址)。

如果您不配置 Symfony 以查找这些标头,您将获得关于客户端 IP 地址、客户端是否通过 HTTPS 连接、客户端端口以及正在请求的主机名的不正确信息。

解决方案:setTrustedProxies()

要解决此问题,您需要告诉 Symfony 要信任哪些反向代理 IP 地址,以及您的反向代理使用哪些标头来发送信息。

您可以通过在您的机器上设置 SYMFONY_TRUSTED_PROXIESSYMFONY_TRUSTED_HEADERS 环境变量来做到这一点。或者,您可以使用以下配置选项来配置它们

1
2
3
4
5
6
7
8
9
10
11
# config/packages/framework.yaml
framework:
    # ...
    # the IP address (or range) of your proxy
    trusted_proxies: '192.0.0.1,10.0.0.0/8'
    # shortcut for private IP address ranges of your proxy
    trusted_proxies: 'private_ranges'
    # trust *all* "X-Forwarded-*" headers
    trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix']
    # or, if your proxy instead uses the "Forwarded" header
    trusted_headers: ['forwarded']

7.1

private_ranges 作为 trusted_proxies 选项的私有 IP 地址范围的快捷方式,在 Symfony 7.1 中引入。

7.2

Symfony 7.2 中引入了对 SYMFONY_TRUSTED_PROXIESSYMFONY_TRUSTED_HEADERS 环境变量的支持。

危险

启用 Request::HEADER_X_FORWARDED_HOST 选项会将应用程序暴露于 HTTP Host 标头攻击。确保代理真的发送了 x-forwarded-host 标头。

Request 对象有几个 Request::HEADER_* 常量,用于精确控制信任哪些来自反向代理的标头。参数是一个位字段,所以您也可以传递您自己的值(例如 0b00110)。

提示

您可以设置 TRUSTED_PROXIES 环境变量,以便在每个环境的基础上配置代理

1
2
# .env
TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8
1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    trusted_proxies: '%env(TRUSTED_PROXIES)%'

危险

当使用 nginx realip 模块 时,“受信任的代理”功能无法按预期工作。在服务 Symfony 应用程序时禁用该模块。

但是,如果我的反向代理的 IP 地址 постоянно 变化怎么办?

某些反向代理(如 AWS Elastic Load Balancing)没有静态 IP 地址,甚至没有您可以使用 CIDR 表示法定位的范围。在这种情况下,您需要 - 非常小心地 - 信任所有代理。

  1. 配置您的 Web 服务器,使其响应来自任何客户端(除了您的负载均衡器)的流量。对于 AWS,可以使用 安全组 来完成此操作。
  2. 一旦您保证流量只会来自您信任的反向代理,请配置 Symfony 以始终信任传入请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # config/packages/framework.yaml
    framework:
        # ...
        # trust *all* requests (the 'REMOTE_ADDR' string is replaced at
        # runtime by $_SERVER['REMOTE_ADDR'])
        trusted_proxies: '127.0.0.1,REMOTE_ADDR'
    
        # you can also use the 'PRIVATE_SUBNETS' string, which is replaced at
        # runtime by the IpUtils::PRIVATE_SUBNETS constant
        # trusted_proxies: '127.0.0.1,PRIVATE_SUBNETS'

7.2

Symfony 7.2 中引入了对 'PRIVATE_SUBNETS' 字符串的支持。

就是这样!至关重要的是,您要阻止来自所有非受信任来源的流量。如果您允许外部流量,他们可能会“欺骗”他们的真实 IP 地址和其他信息。

如果您还在负载均衡器之上使用反向代理(例如 CloudFront),调用 $request->server->get('REMOTE_ADDR') 将不足以解决问题,因为它只会信任直接位于您的应用程序之上的节点(在本例中为您的负载均衡器)。您还需要将任何其他代理(例如 CloudFront IP 范围)的 IP 地址或范围附加到受信任代理的数组中。

子路径/子文件夹中的反向代理

如果您的 Symfony 应用程序在反向代理后运行,并且在子路径/子文件夹中提供服务,Symfony 可能会生成忽略反向代理的子路径/子文件夹的不正确 URL。

要解决此问题,您需要通过设置 X-Forwarded-Prefix 标头将反向代理的子路径/子文件夹路由前缀传递给 Symfony。标头通常可以在您的反向代理配置中配置。配置 X-Forwarded-Prefix 作为受信任的标头,以便能够使用此功能。

X-Forwarded-Prefix 由 Symfony 用于为请求对象的基本 URL 添加前缀,该基本 URL 用于在 Symfony 应用程序中生成绝对路径和 URL。如果没有标头,基本 URL 将仅根据运行 Symfony 的 Web 服务器的配置来确定,当应用程序由反向代理在子路径/子文件夹下提供服务时,这将导致不正确的路径/URL。

例如,如果您的 Symfony 应用程序直接在类似 https://symfony.tld/ 的 URL 下提供服务,并且您希望使用反向代理在 https://public.tld/app/ 下提供应用程序,您需要在反向代理配置中将 X-Forwarded-Prefix 标头设置为 /app/。如果没有标头,Symfony 将根据其服务器基本 URL(例如 /my/route)而不是正确的 /app/my/route 生成 URL,这是通过反向代理访问路由所必需的。

每个反向代理的标头可能不同,因此可以通过在不同子路径/子文件夹下服务的不同反向代理正确处理访问。

使用反向代理时的自定义标头

某些反向代理(如 CloudFrontCloudFront-Forwarded-Proto)可能会强制您使用自定义标头。例如,您有 Custom-Forwarded-Proto 而不是 X-Forwarded-Proto

在这种情况下,您需要在应用程序中尽早设置标头 X-Forwarded-Proto,其值为 Custom-Forwarded-Proto,即在处理请求之前

1
2
3
4
5
6
// public/index.php

// ...
$_SERVER['HTTP_X_FORWARDED_PROTO'] = $_SERVER['HTTP_CUSTOM_FORWARDED_PROTO'];
// ...
$response = $kernel->handle($request);

在隐藏的 SSL 终止后覆盖配置

某些云设置(例如在 Microsoft Azure 中的“Web App for Containers”中运行 Docker 容器)会进行 SSL 终止,并通过 HTTP 联系您的 Web 服务器,但不会更改远程地址,也不会设置 X-Forwarded-* 标头。这意味着 Symfony 的受信任代理功能无法帮助您。

一旦您确保您的服务器只能通过 HTTPS 上的云代理访问,而不能通过 HTTP 访问,您可以覆盖您的 Web 服务器发送给 PHP 的信息。对于 Nginx,这可能看起来像这样

1
2
3
4
5
6
7
location ~ ^/index\.php$ {
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi.conf;
    # Lie to Symfony about the protocol and port so that it generates the correct HTTPS URLs
    fastcgi_param SERVER_PORT "443";
    fastcgi_param HTTPS "on";
}
包括代码示例在内的这项工作,根据 Creative Commons BY-SA 3.0 许可证获得许可。
目录
    版本