結論先講
安全功能盡量放 API Gateway,不要讓每個服務自己做。 Rate limiting 放 nginx 層的效能開銷近乎零,放 Express middleware 會吃掉 5-10% throughput。IP whitelist、OAuth2 驗證、CORS 這些「每個請求都要做的事」,在 Gateway 統一處理一次就好,不要在 5 個服務各做 5 次。
API Gateway 是微服務的大門
外部流量進來的路徑:
用戶 → CDN → Load Balancer → API Gateway → 各個微服務
↑
安全檢查都在這裡做:
- Rate Limiting
- IP Whitelist / Blacklist
- OAuth2 / JWT 驗證
- CORS
- Request Size Limit
- SQL Injection / XSS 過濾
Rate Limiting
放在哪裡:Nginx vs 應用層
根據基礎設施壓測數據,rate limiting 放在不同層的效能差異明顯:
| 實作位置 | Throughput 影響 | 延遲影響 | 說明 |
|---|---|---|---|
Nginx(limit_req_zone) | 近乎零 | < 0.1ms | C 寫的,在記憶體中操作 |
Express middleware(express-rate-limit) | -5~10% | +2-5ms | 每個請求都進 Node.js event loop |
| Redis-based(跨 instance 共享) | -2~3% | +1-2ms | 一次 Redis INCR 操作 |
Nginx Rate Limiting 設定
http {
# 定義限流區域:每個 IP 每秒 10 個請求
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# 針對 API 路徑的限流:每個 IP 每秒 5 個請求
limit_req_zone $binary_remote_addr zone=api_write:10m rate=5r/s;
server {
# 讀取 API:寬鬆一點
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://api-gateway;
}
# 寫入 API:嚴格一點
location /api/orders {
limit_req zone=api_write burst=5 nodelay;
proxy_pass http://api-gateway;
}
}
}分層 Rate Limiting
Layer 1 — Nginx(粗粒度):
每個 IP 每秒 100 個請求 → 擋 DDoS
Layer 2 — API Gateway(中粒度):
每個 API Key 每分鐘 600 個請求 → 擋濫用
Layer 3 — Application(細粒度):
每個用戶每天只能建立 10 筆訂單 → 業務邏輯限制
不要只做一層。Nginx 擋得住暴力攻擊,但擋不住「同一個用戶每秒打 5 次 API」的合法但濫用的行為。
IP Whitelist / Blacklist
用途
Whitelist(白名單):
- Webhook endpoint 只允許特定 IP(Stripe callback IP)
- 管理後台只允許辦公室 IP
Blacklist(黑名單):
- 已知的攻擊 IP
- 爬蟲 IP 段
Nginx 設定
# Webhook 只允許 Stripe 的 IP
location /webhooks/stripe {
allow 3.18.12.63/32;
allow 3.130.192.0/24;
deny all;
proxy_pass http://payment-service;
}
# 管理後台只允許辦公室 IP
location /admin/ {
allow 203.0.113.0/24; # 辦公室
allow 10.0.0.0/8; # VPN
deny all;
proxy_pass http://admin-service;
}注意:IP whitelist 不應該是唯一的安全機制。IP 可以被偽造(X-Forwarded-For),搭配認證使用。
OAuth2 Proxy
把 OAuth2 驗證從每個服務抽出來,統一在 Gateway 做:
沒有 OAuth2 Proxy:
每個服務都要:解析 JWT → 驗證簽名 → 檢查 scope → 查 user info
→ 5 個服務各寫一次一模一樣的邏輯
有 OAuth2 Proxy:
Gateway 做一次驗證 → 把 user info 放到 header 轉發給後端服務
→ 後端服務只要讀 header,不需要自己驗證
# nginx + oauth2-proxy
location /api/ {
auth_request /oauth2/auth;
auth_request_set $user_id $upstream_http_x_auth_request_user;
auth_request_set $user_email $upstream_http_x_auth_request_email;
proxy_set_header X-User-ID $user_id;
proxy_set_header X-User-Email $user_email;
proxy_pass http://api-services;
}後端服務拿到的 request 已經帶有 X-User-ID header,不需要自己驗 JWT。
CORS
CORS 問題的本質:
瀏覽器發 AJAX 請求到不同 domain → 瀏覽器先發 OPTIONS preflight
→ 如果 server 沒有回正確的 CORS header → 瀏覽器擋住請求
放在 Gateway 統一處理
location /api/ {
# CORS headers
add_header Access-Control-Allow-Origin "https://myapp.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
# Preflight
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://api-services;
}不要用 Access-Control-Allow-Origin: *。 你的 API 不是公開的(除非你是做 public API 的 SaaS),明確指定允許的 domain。
Kong vs APISIX vs Traefik
| 功能 | Kong | APISIX | Traefik |
|---|---|---|---|
| 語言 | Lua(OpenResty) | Lua(OpenResty) | Go |
| 效能 | 高 | 最高(etcd-based) | 中高 |
| Plugin 生態 | 最豐富 | 豐富且成長快 | 中等 |
| K8s 整合 | 好 | 好 | 最好(原生 Ingress) |
| Rate Limiting | 內建 | 內建 | 需要 middleware |
| OAuth2 | 內建 plugin | 內建 plugin | 需要 ForwardAuth |
| 學習曲線 | 中等 | 中等 | 低 |
| 額外延遲 | 1-3ms | 0.5-2ms | 1-2ms |
怎麼選
只需要 Ingress + 基本安全 → Traefik(K8s 原生、最簡單)
需要豐富 plugin + 企業支援 → Kong(社群最大)
效能敏感 + 自定義 plugin → APISIX(效能最好)
不想多一層 → 直接用 Nginx + 手寫 config(最輕量)
常見的坑
1. Rate Limit 沒考慮分散式
你有 3 台 API Gateway → 每台各自計算 rate limit
→ 用戶實際上可以打 3 倍的量
解法:用 Redis 做共享計數器(Redis INCR + EXPIRE)
2. CORS 設定被服務覆蓋
Gateway 設了 CORS header → 後端服務又加了一次
→ 瀏覽器收到重複的 CORS header → 某些瀏覽器會報錯
解法:CORS 只在一個地方設,後端服務不要再加
3. Gateway 變成單點故障
所有流量都過 Gateway → Gateway 掛了就全掛
解法:
- Gateway 至少 2 個 instance(HA)
- 前面放 Load Balancer
- 設定 health check + 自動重啟
下一篇
開發者體驗 — 安全搞定了,回到日常開發的痛點:本地跑 5 個微服務 + 各種 middleware,RAM 直接爆掉。怎麼在本地高效開發微服務?
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:微服務安全(一):服務間認證 mTLS vs JWT vs API Key → 下一篇:開發者體驗(一):本地跑 5 個微服務吃掉所有 RAM