package middleware import ( "bytes" "crypto/hmac" "io" "strconv" "strings" "sync" "time" "affiliate_dash/internal/pkg/openlog" "affiliate_dash/internal/pkg/response" "github.com/gin-gonic/gin" ) // SourceOpenAuthConfig 是原上游发货接口鉴权配置,保持 X-Api-Key 兼容。 type SourceOpenAuthConfig struct { APIKey string APISecret string SkewSeconds int64 Debug bool } type sourceNonceStore struct { mu sync.Mutex data map[string]int64 } func newSourceNonceStore() *sourceNonceStore { return &sourceNonceStore{data: make(map[string]int64)} } func (s *sourceNonceStore) seen(nonce string, now, ttl int64) bool { s.mu.Lock() defer s.mu.Unlock() for key, expiresAt := range s.data { if expiresAt < now { delete(s.data, key) } } if expiresAt, ok := s.data[nonce]; ok && expiresAt >= now { return true } s.data[nonce] = now + ttl return false } // SourceOpenAuth 保持现有上游对接签名算法不变:X-Api-Key + 字典序 HMAC。 func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc { if cfg.SkewSeconds <= 0 { cfg.SkewSeconds = 300 } store := newSourceNonceStore() return func(c *gin.Context) { reqID := openlog.EnsureReqID(c) c.Set(openlog.CtxDebug, cfg.Debug) c.Set(openlog.CtxStart, time.Now()) c.Header("X-Request-Id", reqID) if cfg.APIKey == "" || cfg.APISecret == "" { response.ServerError(c, "服务端未配置 OPEN_API_KEY / OPEN_API_SECRET") c.Abort() return } apiKey := c.GetHeader("X-Api-Key") timestamp := c.GetHeader("X-Timestamp") nonce := c.GetHeader("X-Nonce") sign := c.GetHeader("X-Sign") if apiKey == "" || timestamp == "" || nonce == "" || sign == "" { response.Unauthorized(c, "缺少鉴权头:需要 X-Api-Key、X-Timestamp、X-Nonce、X-Sign") c.Abort() return } if apiKey != cfg.APIKey { response.Unauthorized(c, "无效的 API Key") c.Abort() return } if len(nonce) < 8 || len(nonce) > 64 { response.Unauthorized(c, "X-Nonce 长度需在 8~64 之间") c.Abort() return } ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil || abs64(time.Now().Unix()-ts) > cfg.SkewSeconds { response.Unauthorized(c, "请求已过期或 X-Timestamp 格式错误") c.Abort() return } bodyBytes, err := io.ReadAll(c.Request.Body) if err != nil { response.BadRequest(c, "读取请求体失败") c.Abort() return } c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) if store.seen(apiKey+":"+nonce, time.Now().Unix(), cfg.SkewSeconds) { response.Unauthorized(c, "重复的 X-Nonce(请勿重放请求)") c.Abort() return } expected := BuildOpenSign(apiKey, cfg.APISecret, timestamp, nonce, c.Request.Method, c.Request.URL.Path, string(bodyBytes)) if !hmac.Equal([]byte(strings.ToLower(sign)), []byte(expected)) { response.Unauthorized(c, "签名校验失败") c.Abort() return } c.Next() } }