安全中介層全家桶:CORS / CSRF / JWT / rate limit / binder
安全中介層全家桶: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:CORSHandlerWithConfig 依 CORSConfig 設 Access-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 這塊有三道刻意的硬規則:
- secret ≥ 32 bytes:
NewJWTService對短於MinJWTSecretBytes(32,對齊 SHA-256 輸出長度)的 secret 直接回ErrJWTSecretTooShort;config.Validate在載入設定時就擋。短 key 會弱化 HMAC。 - 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 帶 typ(access / 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 翻新計劃,新漆未乾。