FastAPI 中介層(Middleware)
主題:TrustedHost Middleware
簡介
在 Web 應用程式的安全防護中,主機名稱(Host)驗證 是最容易被忽視卻相當關鍵的一環。
若 API 允許任意 Host 標頭,攻擊者可以藉由 Host Header Injection 進行跨站腳本(XSS)或偽造連結,甚至繞過 CORS 設定。
FastAPI 內建的 TrustedHostMiddleware 正是為了防止這類問題而設計的。它會在每一次請求進入路由前,檢查 Host 標頭是否符合我們事先定義的白名單,若不符合則直接回傳 400 Bad Request。
本篇文章將從 概念說明、實作範例、常見陷阱與最佳實踐,以及 實際應用場景 四個面向,完整介紹如何在 FastAPI 中正確使用 TrustedHostMiddleware,讓你的 API 在部署到生產環境時多一層防護。
核心概念
1. 為什麼需要 TrustedHost Middleware?
- 防止 Host Header Injection:不受信任的 Host 可能被用來生成錯誤的連結或重定向 URL。
- 保護 CSRF 防護機制:許多 CSRF token 會依賴正確的 Host,若 Host 被偽造,攻擊者可能繞過檢查。
- 符合企業安全政策:在多租戶或子域名架構下,只允許特定域名存取服務是基本要求。
簡單來說,TrustedHost Middleware 就像是 API 的「門衛」,只允許符合條件的訪客通過。
2. Middleware 的運作原理
FastAPI 使用 Starlette 作為底層框架,所有 Middleware 都遵循 ASGI(Asynchronous Server Gateway Interface)規範。TrustedHostMiddleware 的流程大致如下:
- 收到請求 →
- 讀取
Host標頭 → - 與設定的白名單做比對 →
- 若匹配 → 請求繼續往下傳遞至路由或其他 Middleware。
- 若不匹配 → 直接回傳
400 Bad Request(或自訂回應)。
3. 基本使用方式
在 FastAPI 應用程式建立後,只需要把 TrustedHostMiddleware 加入 app 的 middleware 列表即可。最常見的參數如下:
| 參數 | 型別 | 說明 |
|---|---|---|
allowed_hosts |
list[str] |
允許的 Host 名稱,支援 * 通配符(如 *.example.com)。 |
www_redirect |
bool (預設 False) |
若請求的 Host 為 example.com,自動導向 www.example.com。 |
exclude_hosts |
list[str] (可選) |
想要排除的 Host(較少使用)。 |
程式碼範例
下面提供 5 個實用範例,從最簡單的設定到進階的自訂回應與測試,幫助你快速上手。
範例 1:最基本的 TrustedHost 設定
# main.py
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
# 只允許 localhost 與 127.0.0.1
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["localhost", "127.0.0.1"]
)
@app.get("/")
async def root():
return {"message": "Hello, TrustedHost!"}
說明:當你在本機開發時,若有人直接以
http://malicious.com來呼叫 API,將會收到400 Bad Request。
範例 2:使用通配符允許子域名
# main.py
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
# 允許所有 *.example.com 以及根域 example.com
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["example.com", "*.example.com"]
)
@app.get("/info")
async def info():
return {"host": "valid"}
說明:
*.example.com會匹配api.example.com、admin.example.com等子域名,適合多服務共用同一個根域的情境。
範例 3:自訂錯誤回應
預設的錯誤訊息僅是簡短的文字,若想回傳 JSON 格式,須自行覆寫 HTTPException 處理。
# main.py
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["myapp.com", "api.myapp.com"]
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
if exc.status_code == 400 and "Invalid host header" in exc.detail:
return JSONResponse(
status_code=400,
content={"error": "Invalid Host header", "detail": exc.detail}
)
# 其他 HTTPException 照常處理
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
@app.get("/status")
async def status():
return {"msg": "All good!"}
說明:此範例捕捉到由
TrustedHostMiddleware拋出的 400 錯誤,改以 JSON 形式回傳,讓前端或 API 使用者更容易解析。
範例 4:結合 www_redirect 功能
# main.py
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["example.com", "www.example.com"],
www_redirect=True # 自動把 example.com 轉成 www.example.com
)
@app.get("/")
async def home():
return {"msg": "Welcome to www.example.com"}
說明:若使用者輸入
http://example.com,FastAPI 會回傳 307 Temporary Redirect,導向http://www.example.com,確保網址統一性,有助於 SEO 與快取。
範例 5:在測試環境中動態加入允許的 Host
在 CI/CD pipeline 中,我們往往會使用臨時產生的容器名稱或隨機子域名。以下示範如何在程式啟動時讀取環境變數,動態設定 allowed_hosts。
# main.py
import os
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
# 從環境變數取得額外的 host(以逗號分隔)
extra_hosts = os.getenv("EXTRA_ALLOWED_HOSTS", "")
extra_hosts_list = [h.strip() for h in extra_hosts.split(",") if h]
# 基本的 production host
base_hosts = ["myapp.com", "api.myapp.com"]
allowed = base_hosts + extra_hosts_list
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=allowed
)
@app.get("/ping")
async def ping():
return {"msg": "pong"}
說明:在 GitHub Actions 或 Docker Compose 中,只要設定
EXTRA_ALLOWED_HOSTS=container-12345.local,測試就會通過,而不必硬編碼。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 最佳實踐 |
|---|---|---|
忘記加入 www_redirect |
部署後使用者可能以 example.com 或 www.example.com 兩種方式存取,導致快取分散。 |
若公司政策要求單一網址,啟用 www_redirect=True。 |
| 使用過寬鬆的通配符 | *(全部允許)會讓 Middleware 失去意義。 |
只允許必要的子域名,例如 *.example.com,避免 *。 |
| 環境變數未正確解析 | 在容器化部署時,環境變數的空值會產生 [''],造成 allowed_hosts 包含空字串,導致所有請求被拒絕。 |
在程式中檢查空字串或使用 if extra_hosts: 再分割。 |
在開發環境忘記加入 localhost |
開發時常用 127.0.0.1 或 localhost,若未列入白名單會頻繁收到 400。 |
為開發環境建立專屬設定檔或使用 if DEBUG: 動態加入。 |
| 未針對 HTTP/2 或 gRPC 設定 Host | 某些服務使用 HTTP/2 的 :authority 標頭,若未考慮會被視為非法 Host。 |
確認客戶端傳送正確的 Host,或在 Proxy 前做好轉發。 |
進階最佳實踐
- 分層驗證:除了
TrustedHostMiddleware,還可以結合 CORS、CSRF Token 與 API 金鑰,形成多層防禦。 - 日誌紀錄:將被拒絕的 Host 寫入日誌,便於事後分析攻擊來源。
import logging logger = logging.getLogger("uvicorn.error") # 在自訂例外處理器中加入 logger.warning(...) - 自動化測試:在 CI 中加入針對不在白名單的 Host 測試,確保 Middleware 不會因程式變更而失效。
- 最小化白名單:只允許實際需要的 Host,避免因新增測試環境而忘記移除。
實際應用場景
場景 1:多租戶 SaaS 平台
公司提供 SaaS 服務,客戶會有各自的子域名 client1.myservice.com、client2.myservice.com。
透過 TrustedHostMiddleware 僅允許 *.myservice.com,即可防止外部網站偽造 Host 直接呼叫 API,降低資料外洩風險。
場景 2:內部微服務間的安全通訊
在 Kubernetes 中,各微服務以服務名稱作為 Host(例如 auth-service.namespace.svc.cluster.local)。
將這些名稱加入 allowed_hosts,確保只有叢集內部的服務能對外部 API 發起請求,避免外部流量偽裝成內部服務。
場景 3:使用 CDN 或反向代理
當 API 前端放置 Cloudflare、AWS CloudFront 或 Nginx 代理時,實際的 Host 仍是原始域名。
若 CDN 設定了自訂的 Host(例如 api.cdn.mycompany.com),必須同時將其加入白名單,否則會被 Middleware 拒絕。
場景 4:測試環境的臨時子域名
CI 流水線會在每次測試時產生臨時子域名 pr-1234.myapp.dev。
透過環境變數 EXTRA_ALLOWED_HOSTS 動態加入,確保測試自動化不會因 Host 驗證失敗而中斷。
總結
- TrustedHostMiddleware 是 FastAPI 防止 Host Header Injection 的第一道防線,只要設定正確的
allowed_hosts,即可阻擋絕大多數的偽造請求。 - 使用 通配符、www_redirect 與 自訂例外處理 能讓 Middleware 更貼合實務需求。
- 避免常見的 過寬通配符、環境變數空值、開發環境遺漏 localhost 等陷阱,並配合 日誌、測試與分層驗證,才能在生產環境中發揮最大效益。
- 在多租戶、微服務、CDN 以及 CI/CD 測試等情境下,適當調整白名單與動態設定,讓安全與開發效率同時提升。
透過本文的概念說明與實作範例,你已經掌握了在 FastAPI 中使用 TrustedHost Middleware 的完整流程。現在就把這層防護加入你的專案,讓 API 更安全、更可靠吧! 🚀