結論先講
內部服務間認證,首選 mTLS(透過 Service Mesh 自動管理),次選 JWT。 mTLS 安全性最高,Service Mesh(Istio/Linkerd)可以自動處理憑證輪替,開發者幾乎無感。JWT 適合不想上 Service Mesh 的團隊,效能開銷約 2ms,可以順便傳遞用戶身份。API Key 只適合內部工具或低安全性場景——洩漏一把 key 就全部完蛋。
為什麼內部通訊也需要認證
常見的誤解:
「我們的服務都在同一個 VPC / K8s cluster 裡面,外面的人進不來,
所以服務之間不需要認證。」
現實:
1. 一個服務被打穿(RCE 漏洞)→ 攻擊者用它呼叫所有內部服務
2. 開發環境連到 production 的內部 API(設定搞錯)
3. 離職員工知道內部 API 格式,從同 VPC 的跳板機打過去
Zero Trust Architecture 的核心原則:不因為「在內網」就信任請求。每個請求都要驗證身份。
三種認證方式比較
API Key
Order Service → Payment Service
Header: X-API-Key: sk_live_abc123def456
Payment Service 檢查:
if (req.headers['x-api-key'] !== process.env.PAYMENT_API_KEY) {
return 401;
}
效能:幾乎零開銷(< 1ms,就是一個字串比對)。
問題:
- Key 是靜態的,洩漏了就完蛋(除非立刻輪替)
- 沒有過期時間,一把 key 用到天荒地老
- 無法帶用戶身份資訊(只知道「是 Order Service 來的」)
- 所有 instance 共用同一把 key,無法追蹤是哪台發的
JWT
Order Service → Payment Service
Header: Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Payment Service 驗證:
1. 解析 JWT
2. 驗證簽名(用公鑰)
3. 檢查 iss(發行者)、exp(過期時間)、aud(受眾)
4. 確認 scope 包含 payment:create
效能:RS256 驗證約 2ms,HS256 約 0.5ms。相比一次 DB 查詢 5-20ms,完全可以接受。
優點:
- 自帶過期時間(通常 5-15 分鐘)
- 可以帶用戶身份(sub claim)——Payment Service 知道這筆付款是哪個用戶發起的
- 不需要每次去中央服務驗證(公鑰本地就能驗)
JWT 傳播模式:
用戶 → API Gateway(驗證用戶的 JWT)
→ Gateway 簽發 internal JWT(包含 user_id + service identity)
→ Order Service → Payment Service(帶著 internal JWT)
Payment Service 從 JWT 拿到:
- 哪個服務呼叫的(iss: order-service)
- 代表哪個用戶(sub: user_12345)
- 有什麼權限(scope: payment:create)
mTLS(Mutual TLS)
一般的 TLS(HTTPS):
Client 驗證 Server 的憑證 → 「我確定我在跟真正的 Payment Service 說話」
Mutual TLS:
Client 驗證 Server 的憑證 → 「我確定我在跟真正的 Payment Service 說話」
Server 也驗證 Client 的憑證 → 「我確定是 Order Service 在呼叫我」
雙向驗證 → 兩邊都確認對方身份
效能:TLS handshake 約 5-10ms(首次),之後連線復用幾乎零開銷。
優點:
- 安全性最高(密碼學等級的身份認證)
- 通訊同時加密(防竊聽、防篡改)
- 憑證可以自動輪替(Service Mesh 處理)
缺點:
- 手動管理憑證是噩夢(每個服務都要 cert + key + CA)
- 沒有 Service Mesh 的話,運維成本極高
Service Mesh 讓 mTLS 變簡單
沒有 Service Mesh:
你要自己管理 N 個服務的 N 張憑證
每張憑證有過期時間,要定期輪替
忘記輪替 → 憑證過期 → 服務斷線 → P1 事故
有 Service Mesh(Istio / Linkerd):
Sidecar proxy 自動處理 mTLS
憑證自動簽發、自動輪替
開發者完全不用碰憑證 ← 這才是重點
Istio 的 mTLS 設定
# 一行搞定:所有服務間通訊強制 mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT # STRICT = 必須 mTLS,PERMISSIVE = 可選就這樣。Istio 的 sidecar(Envoy proxy)會自動幫每個 Pod 管理憑證,開發者寫程式碼時完全不用管 TLS。
效能比較
根據壓測數據,三種認證方式的額外延遲:
| 認證方式 | 額外延遲 | 適用場景 |
|---|---|---|
| API Key | < 1ms | 內部工具、低安全性 |
| JWT(RS256) | ~2ms | 中等安全性、需要傳遞用戶身份 |
| JWT(HS256) | ~0.5ms | 效能敏感但安全性要求稍低 |
| Session(DB lookup) | ~5ms | 單體架構殘留,不建議用在微服務 |
| mTLS(首次 handshake) | 5-10ms | 首次建立連線 |
| mTLS(連線復用) | ~0ms | 之後的請求幾乎零開銷 |
重點:mTLS 看起來首次延遲高,但因為服務間通常會復用 HTTP/2 連線,實際上長期來看開銷最低。
我的建議
如果你已經用 K8s + Service Mesh:
→ mTLS(自動的,不用多想)
→ 加上 JWT 傳遞用戶身份(如果需要)
如果你沒有 Service Mesh:
→ JWT(最好的效能 / 安全性平衡)
→ 用 API Gateway 統一簽發 internal JWT
如果你只有 3 個服務、團隊 5 個人:
→ API Key 先頂著
→ 但要有輪替機制(至少每季換一次)
→ 準備好之後遷移到 JWT
常見的坑
1. JWT 忘記設過期時間
簽發一個永不過期的 JWT → 跟 API Key 一樣危險
建議:internal JWT 過期時間 5-15 分鐘,搭配 refresh 機制
2. mTLS 憑證過期
手動管理 mTLS → 某天凌晨 3 點憑證過期 → 全部服務斷線
解法:用 Service Mesh 自動輪替,或用 cert-manager 搭配告警
3. API Key 寫死在程式碼裡
git log 裡面有 API Key → 就算刪了也能從歷史找到
解法:Key 放環境變數或 Secret Manager(AWS Secrets Manager / Vault)
下一篇
API Gateway 安全 — 服務間的認證搞定了,但外部流量呢?Rate limiting、IP whitelist、OAuth2 proxy 這些安全功能,該放在 API Gateway 還是每個服務自己做?
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:Zero Downtime Deployment:Rolling Update vs Blue-Green vs Canary → 下一篇:微服務安全(二):API Gateway 的安全功能