This commit is contained in:
toom1996
2026-09-07 00:04:01 +08:00
parent 6a5a4378ab
commit 10d8a96e8c
29 changed files with 5349 additions and 0 deletions

View File

@ -0,0 +1,56 @@
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()
}
}