FastAPI – 安全性(Security)
主題:HTTPS 與 CORS 配置
簡介
在 Web API 的開發過程中,資料傳輸的安全性與**跨來源資源共享(CORS)**的正確設定是不可或缺的兩大基礎。
- 若 API 直接以 HTTP 提供服務,任何人在傳輸途中都可能竊取或竄改資料,對使用者與企業都會造成嚴重風險。
- 而前端應用(如 React、Vue)與後端 API 常常位於不同的網域或埠號,未妥善設定 CORS 會導致瀏覽器阻擋合法請求,影響使用者體驗與開發效率。
FastAPI 本身支援簡潔的安全機制,配合 uvicorn、gunicorn 或 nginx 等反向代理,我們可以快速完成 HTTPS 與 CORS 的設定,讓服務既安全又易於整合前端。本文將從概念說明、實作範例、常見陷阱與最佳實踐,完整介紹在 FastAPI 中如何正確配置 HTTPS 與 CORS。
核心概念
1️⃣ 為什麼需要 HTTPS
- 加密傳輸:TLS (Transport Layer Security) 會把所有請求與回應內容加密,防止中間人攻擊(MITM)。
- 驗證伺服器身份:憑證(Certificate)由受信任的 CA 簽發,讓客戶端確定連線的是正確的伺服器。
- SEO 與瀏覽器要求:現代瀏覽器會對未使用 HTTPS 的 API 標示「不安全」,影響使用者信任度。
在開發階段,我們可以使用 自簽憑證 進行測試;上線時則必須使用正式的憑證(如 Let's Encrypt)。
2️⃣ CORS 基礎
CORS 是瀏覽器的安全機制,允許伺服器告訴瀏覽器哪些來源(origin)可以存取資源。
- Origin:由
scheme://host:port組成,例如https://example.com:443。 - Preflight Request:對於需要特殊權限的請求(如
PUT、DELETE、或自訂 Header),瀏覽器會先發送OPTIONS請求確認是否允許。
FastAPI 內建 fastapi.middleware.cors.CORSMiddleware,只要在應用程式建立時加入即可。
3️⃣ HTTPS 與 CORS 的關係
- HTTPS 必須先於 CORS 生效:若伺服器本身不支援 HTTPS,瀏覽器會直接阻擋請求,CORS 設定再怎麼正確也無法發揮作用。
- CORS Header 必須在 TLS 加密後傳送:這確保了來源驗證資訊不會被竊聽或竄改。
程式碼範例
以下範例示範如何在 FastAPI 中同時設定 HTTPS(使用自簽憑證)與 CORS,並提供常見的變化寫法。
📌 範例 1:最簡單的 HTTPS + CORS 設定(開發環境)
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# -------- CORS 設定 ----------
origins = [
"http://localhost:3000", # React 開發伺服器
"http://127.0.0.1:3000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # 允許的來源清單
allow_credentials=True, # 允許 Cookie / Authorization Header
allow_methods=["*"], # 允許所有 HTTP 方法
allow_headers=["*"], # 允許所有 Header
)
@app.get("/ping")
async def ping():
return {"message": "pong"}
在專案根目錄產生自簽憑證(cert.pem、key.pem):
# 產生自簽憑證(測試用)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem -subj "/CN=localhost"
使用 uvicorn 直接啟動 HTTPS:
uvicorn main:app --host 0.0.0.0 --port 8443 --ssl-keyfile=key.pem --ssl-certfile=cert.pem
重點:開發階段只需要自簽憑證即可;若要在瀏覽器測試,請在瀏覽器中加入例外信任此憑證。
📌 範例 2:使用 Nginx 作為反向代理,提供正式的 HTTPS
Nginx 設定檔(/etc/nginx/sites-available/api.conf)
server {
listen 80;
server_name api.example.com;
# 讓 HTTP 自動導向 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8000; # uvicorn 只跑在 http
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
uvicorn 啟動指令(僅 HTTP)
uvicorn main:app --host 127.0.0.1 --port 8000
技巧:
proxy_set_header X-Forwarded-Proto $scheme;讓 FastAPI 能透過request.url.scheme正確取得https,避免在產生絕對 URL 時出錯。
📌 範例 3:動態取得允許的 Origin(多租戶環境)
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 允許所有子網域的動態判斷
def get_allowed_origins(request: Request):
origin = request.headers.get("origin")
if origin and origin.endswith(".mycompany.com"):
return [origin]
return [] # 不允許其他來源
@app.middleware("http")
async def cors_dynamic(request: Request, call_next):
allowed = get_allowed_origins(request)
if allowed:
app.add_middleware(
CORSMiddleware,
allow_origins=allowed,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
response = await call_next(request)
return response
說明:此範例展示如何根據請求的
Origin動態決定是否加入 CORS 中介軟體,適合 SaaS 多租戶系統。注意:在高併發環境中頻繁呼叫add_middleware可能造成效能問題,建議在啟動時就建立完整的白名單或使用自訂 Middleware 處理 Header。
📌 範例 4:在 FastAPI 中手動設定 CORS Header(不使用 Middleware)
from fastapi import FastAPI, Response
app = FastAPI()
@app.options("/{full_path:path}")
async def preflight(full_path: str, response: Response):
# 手動回傳 CORS Preflight 必要的 Header
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Authorization,Content-Type"
response.headers["Access-Control-Max-Age"] = "86400"
return response
適用情境:當你只想允許非常少數的路由或需要與其他非 FastAPI 框架共存時,手動設定 Header 能提供更細緻的控制。
📌 範例 5:在測試環境中關閉 CORS(單元測試)
# test_main.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_ping():
response = client.get("/ping")
assert response.status_code == 200
assert response.json() == {"message": "pong"}
# 確認 CORS Header 正確回傳
assert response.headers["access-control-allow-origin"] == "*"
說明:
TestClient會自動使用app的 Middleware,讓我們在單元測試時也能驗證 CORS 設定是否正確。
常見陷阱與最佳實踐
| 常見問題 | 為何會發生 | 解決方式 | 最佳實踐 |
|---|---|---|---|
| 瀏覽器顯示「Mixed Content」 | 前端使用 HTTPS,但 API 仍以 HTTP 提供 | 透過 Nginx 或其他反向代理提供 HTTPS,或在開發時使用 --ssl-* 參數 |
務必在所有環境使用相同的協議(全站 HTTPS) |
CORS 設定過於寬鬆(allow_origins=["*"]) |
為了測試方便直接放行所有來源 | 在正式環境改為白名單或動態檢查 | 最小權限原則:僅允許需要的前端來源 |
Preflight 請求失敗(405 Method Not Allowed) |
未正確加入 OPTIONS 處理或 CORS Middleware 未啟用 |
確認 CORSMiddleware 已加入,或自行實作 @app.options |
始終允許 OPTIONS,尤其在使用自訂 Header 時 |
| HTTPS 只在開發機可用,部署後出現 502 | 反向代理未正確轉發 X-Forwarded-Proto,導致 FastAPI 判斷為 HTTP |
在 Nginx 加入 proxy_set_header X-Forwarded-Proto $scheme; |
在程式內使用 request.url.scheme 判斷,或在 uvicorn 端加入 --proxy-headers |
| 憑證過期未自動更新 | 手動產生的自簽憑證或 Let's Encrypt 未設定自動續期 | 使用 certbot renew --dry-run 測試,自動化腳本加入 crontab |
自動化憑證管理,避免服務中斷 |
其他最佳實踐
- 使用 HTTP/2:在 Nginx 開啟
http2,提升效能與安全性。 - 啟用 HSTS:在 Nginx 加入
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;。 - 限制 TLS 版本:只允許 TLS 1.2+,避免舊版協議的弱點。
- 定期掃描安全漏洞:使用
sslscan、nmap檢查憑證與加密套件。 - 在 API 回傳前過濾敏感資訊:即使使用 HTTPS,仍應避免在回應中洩漏密碼、金鑰等。
實際應用場景
| 場景 | 為何需要 HTTPS & CORS | 實作要點 |
|---|---|---|
| 行動 App 與後端 API | 手機網路常在公共 Wi‑Fi,必須防止資料被竊聽 | 使用正式憑證、允許 app.example.com 作為唯一 Origin |
| SPA 前端(React/Vue)部署於 CDN | 前端與 API 分屬不同子網域,跨域請求不可避免 | 在 CDN 設定 Access-Control-Allow-Origin,FastAPI 只允許該 CDN 網域 |
| 多租戶 SaaS 平台 | 每個客戶有自己的子網域(client1.myapp.com) |
動態判斷 Origin,或在 Nginx 以變數 map 產生白名單 |
| 內部微服務間的呼叫 | 服務間傳遞機密資料,需要加密保護 | 使用內部 CA 簽發憑證,並在 Nginx 中僅允許特定 IP 的 CORS(或直接關閉) |
| 第三方合作夥伴 API | 允許合作夥伴的前端直接呼叫你的 API | 在 CORS 白名單中加入合作夥伴的網域,並使用 OAuth2 或 API Key 做身份驗證 |
總結
- HTTPS 為 API 提供了傳輸層的加密與身份驗證,是現代 Web 應用的基本要求。
- CORS 則是瀏覽器層面的授權機制,必須在 HTTPS 正常運作後正確設定,才能讓前端安全地存取 API。
- FastAPI 透過
CORSMiddleware以及與 uvicorn、Nginx 的配合,能在 開發、測試、上線 三個階段快速完成安全配置。 - 最佳實踐:使用正式憑證、最小化 CORS 白名單、啟用 HSTS、限制 TLS 版本,並在部署前做好自動化測試與安全掃描。
掌握了 HTTPS 與 CORS 的正確配置後,你的 FastAPI 專案將具備 資料保密性、完整性與跨域可用性,為下一階段的認證、授權(OAuth2、JWT)奠定堅實基礎。祝開發順利,服務安全!