結論先講

內部服務間認證,首選 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 的安全功能