安全中介層全家桶:CORS / CSRF / JWT / rate limit / binder

安全不是一個 middleware,是一整套。gortex 把 body 上限、CORS、CSRF、JWT、rate limit、log 遮蔽整合進框架層,業務端不必每次重配。這篇逐個拆。

前置:A1 net/http 與 middleware。

Binder:body 上限

最直接的不可信輸入就是 request body。Binder 把 JSON body 卡在 1 MiB(DefaultMaxJSONBodyBytes = 1 << 20),而且用 http.MaxBytesReader 在讀的當下就擋,超過上限直接報錯,不會先把整包灌進記憶體:

const DefaultMaxJSONBodyBytes int64 = 1 << 20 // 1 MiB
limited := http.MaxBytesReader(c.Response(), originalBody, limit)
// 解碼 limited;body 超過 limit 會在這裡失敗,而不是 OOM

multipart 另有上限(依框架文件為 32 MiB)。這是「把不可信輸入卡在邊界」最基本的一招,擋掉 oversize-body 的 DoS。

CORS / CSRF

CORS:CORSHandlerWithConfigCORSConfigAccess-Control-Allow-Origin / -Methods / -Credentials 等標頭,並補 Vary。注意預設 AllowOrigins["*"],方便開發,上線該收緊成白名單。能在框架層改一次預設、所有服務一致,正是 B1 講的依賴集中管理。

CSRF:用 double-submit token。產一個隨機 token(預設 32 bytes)放 cookie,client 必須在 header 回傳同一個;對不上是 ErrCSRFTokenMismatch、沒帶是 ErrCSRFTokenMissing,都回 403。cookie 的 SameSite 預設 Lax,安全路由可用 Skipper 跳過。

JWT:32-byte、HS256、typ

JWT 這塊有三道刻意的硬規則:

  1. secret ≥ 32 bytesNewJWTService 對短於 MinJWTSecretBytes(32,對齊 SHA-256 輸出長度)的 secret 直接回 ErrJWTSecretTooShortconfig.Validate 在載入設定時就擋。短 key 會弱化 HMAC。
  2. HS256-only:驗證時 keyFuncHS256 檢查 token.Method == jwt.SigningMethodHS256,連 HS384/HS512 都拒。
func (s *JWTService) keyFuncHS256(token *jwt.Token) (any, error) {
    if token.Method != jwt.SigningMethodHS256 {
        return nil, ErrUnexpectedSigningMethod // 擋演算法混淆攻擊
    }
    return s.secret, nil
}

為什麼重要:只用 *jwt.SigningMethodHMAC 做斷言會連帶接受其他 HMAC 演算法,是經典的 algorithm-confusion 漏洞。鎖死 HS256 才安全。 3. typ claim:token 帶 typaccess / refresh)。把 refresh token 拿去當 access 用會回 ErrInvalidTokenType;舊版沒有 typ 的 token 一律拒收。

Rate limit 與 TrustedProxies

rate limiter 用 token bucket 對每個 IP 限流,背景 goroutine 定期清過期 entry,並送出 X-RateLimit-Limit / -Remaining / -Reset,超限時加 Retry-After

但「按 IP 限流」的前提是 IP 可信。client 可以隨手塞一個假的 X-Forwarded-For 來換 IP、繞過限流。所以要配 TrustedProxies(一組 CIDR):只有來自信任 proxy 的請求,X-Forwarded-For / X-Real-IP 才採信;否則忽略轉發標頭、用真正的 RemoteAddr。不設這個,per-IP 限流形同虛設。

log 遮蔽敏感資料

log 會不小心把密碼、token 印出來。gortex 的 logger 用 BodyRedactor 把請求 body 裡的敏感欄位遮掉(預設 DefaultBodyRedactor),header / query 也比照處理。

這裡有個要直說的限制:LogResponseBody 是 no-op,原始碼註解明寫 deprecated、不生效,框架不支援記錄回應 body。4xx / 5xx 則是用 B4 那個 tracked responseWriter 拿到的真實狀態碼,分別記在 Warn / Error。會記什麼、不會記什麼,講清楚比假裝全能好。

重點整理

  • Binder 用 http.MaxBytesReader 把 JSON body 卡在 1 MiB(multipart 另有上限),擋 oversize DoS。
  • CORS 可設允許來源(預設 *,上線應收緊);CSRF 用 double-submit token(預設 32 bytes、SameSite Lax),缺 / 錯 → 403。
  • JWT:secret ≥ 32 bytes、HS256-only(擋 algorithm confusion)、typ 分 access / refresh,舊的無 typ token 一律拒。
  • rate limit 發 X-RateLimit-* / Retry-After;但按 IP 限流要配 TrustedProxies,否則被偽造 X-Forwarded-For 繞過。
  • log 遮蔽請求 body 敏感欄位;LogResponseBody 是 no-op,4xx / 5xx 用 tracked writer 的真實狀態記 Warn / Error。

原始碼:yshengliao/gortex


大綱 Sheng,內文 Claude 協助 · 環境 Go 1.25(gortex go.mod)· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃,新漆未乾。