package signature import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "strconv" "time" ) // Validator 验证签名 type Validator struct { conf SignatureConf nonceStore NonceStore } func NewValidator(conf SignatureConf, store NonceStore) *Validator { return &Validator{conf: conf, nonceStore: store} } func (v *Validator) windowSeconds() int64 { if v.conf.ValidWindowSeconds <= 0 { return 300 } return v.conf.ValidWindowSeconds } // Validate 验证请求签名,返回具体错误便于调用方映射到 xerr 错误码 func (v *Validator) Validate(ctx context.Context, appId, timestamp, nonce, signature, stringToSign string) error { // 1. appId 是否存在 secret, ok := v.conf.AppSecrets[appId] if !ok { return ErrSignatureMissing } // 2. 时间窗口 ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return ErrSignatureExpired } diff := time.Now().Unix() - ts if diff < 0 { diff = -diff } if diff > v.windowSeconds() { return ErrSignatureExpired } // 3. Nonce 防重放(Redis 不可用时降级,仅时间窗口) replayed, err := v.nonceStore.SetIfNotExists(ctx, appId, nonce, v.windowSeconds()+60) if err == nil && replayed { return ErrSignatureReplay } // 4. HMAC 验签(防时序攻击) mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(stringToSign)) expected := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(expected), []byte(signature)) { return ErrSignatureInvalid } return nil }