110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
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
|
||
}
|