如何配置 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
Symfony 7.1 中引入了 private_ranges
作為 trusted_proxies
選項的私有 IP 位址範圍的快捷方式。
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-Forwarded-Proto
的 CloudFront)可能會強制您使用自訂標頭。例如,您有 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";
}