Files
backend_v2/internal/middleware/ingest_auth.go
toom1996 10d8a96e8c update
2026-09-07 00:04:01 +08:00

57 lines
1.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"io"
"net/http"
"strings"
"fashionapi/internal/pkg/hmac"
"fashionapi/internal/repository"
"github.com/gin-gonic/gin"
)
// IngestAuthConfig 爬虫上报接口验签所需依赖。
type IngestAuthConfig struct {
Secret string
TTL int
Nonces repository.IngestRepository // 复用的 nonce 表(与任务队列表同库)
}
// IngestAuth 校验爬虫上报的 HMAC 签名 + nonce 防重放。
//
// 流程:读原始 body → 校验 X-Signature / X-Timestamp / X-Nonce(签名 + ±TTL 时间戳)
// → 用 nonce 表 ReserveNonce 去重(已存在即重放,返回 409)→ 通过则放行。
// 失败一律返回 401(签名/时间戳)或 409(重放),不泄露具体原因。
func IngestAuth(cfg IngestAuthConfig) gin.HandlerFunc {
return func(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "read body failed"})
return
}
// 还原 body,供后续 handler 再读
c.Request.Body = io.NopCloser(strings.NewReader(string(body)))
sig := c.GetHeader("X-Signature")
ts := c.GetHeader("X-Timestamp")
nonce := c.GetHeader("X-Nonce")
if !hmac.Verify(cfg.Secret, string(body), sig, ts, nonce, cfg.TTL) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
return
}
ok, err := cfg.Nonces.ReserveNonce(c.Request.Context(), nonce)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "nonce store error"})
return
}
if !ok {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": "replay detected"})
return
}
c.Next()
}
}