如何配置 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_PROXIES
和 SYMFONY_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_PROXIES
和 SYMFONY_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 表示法定位的范围。在这种情况下,您需要 - 非常小心地 - 信任所有代理。
- 配置您的 Web 服务器,使其不响应来自任何客户端(除了您的负载均衡器)的流量。对于 AWS,可以使用 安全组 来完成此操作。
一旦您保证流量只会来自您信任的反向代理,请配置 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,这是通过反向代理访问路由所必需的。
每个反向代理的标头可能不同,因此可以通过在不同子路径/子文件夹下服务的不同反向代理正确处理访问。
使用反向代理时的自定义标头
某些反向代理(如 CloudFront 和 CloudFront-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";
}