package signer import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "log" "net/url" "sort" "strings" "time" ) // Config 签名配置 type Config struct { AccessKey string SecretKey string Host string // https://jimeng.jianying.com } // Request 签名请求信息 type Request struct { Method string // 恒为 POST Path string // 如 /agent_openapi/v1/video/submit Query url.Values // 通常为空 Body []byte // JSON 序列化后的请求体 ContentType string // 恒为 application/json } // Headers 生成鉴权相关请求头 func (c *Config) Headers(req *Request) (map[string]string, error) { timestamp := fmt.Sprintf("%d", time.Now().Unix()) nonce, err := randomNonce() if err != nil { return nil, err } bodySHA := sha256Hex(req.Body) canonicalQuery := canonicalQueryString(req.Query) contentType := req.ContentType if contentType == "" { contentType = "application/json" } stringToSign := strings.Join([]string{ req.Method, req.Path, canonicalQuery, contentType, bodySHA, timestamp, nonce, c.AccessKey, }, "\n") signature := hmacSHA256Base64URL(c.SecretKey, stringToSign) // 调试日志:打印签名详情,便于排查 10006 错误 log.Printf("[SIGN] Path=%s BodyLen=%d BodySHA256=%s", req.Path, len(req.Body), bodySHA) log.Printf("[SIGN] StringToSign=\n%s", stringToSign) log.Printf("[SIGN] Signature=%s", signature) return map[string]string{ "X-Agent-Access-Key": c.AccessKey, "X-Agent-Timestamp": timestamp, "X-Agent-Nonce": nonce, "X-Agent-Body-SHA256": bodySHA, "X-Agent-Signature-Method": "HMAC-SHA256", "X-Agent-Version": "v1", "X-Agent-Signature": signature, }, nil } func randomNonce() (string, error) { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil } func sha256Hex(data []byte) string { sum := sha256.Sum256(data) return hex.EncodeToString(sum[:]) // 小写 } // query 按参数名 ASCII 升序,key/value 各自 RFC3986 编码后 & 连接 func canonicalQueryString(v url.Values) string { if len(v) == 0 { return "" } keys := make([]string, 0, len(v)) for k := range v { keys = append(keys, k) } sort.Strings(keys) var parts []string for _, k := range keys { for _, val := range v[k] { parts = append(parts, escape(k)+"="+escape(val)) } } return strings.Join(parts, "&") } // RFC3986:空格 -> %20,~ 不编码 func escape(s string) string { return url.QueryEscape(s) } func hmacSHA256Base64URL(key, data string) string { mac := hmac.New(sha256.New, []byte(key)) mac.Write([]byte(data)) return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) // Raw 即无 padding }