云应用 伙伴 API 签名方式

云应用访问伙伴接口采用 RSA 签名方式,公钥信息同 License JWT 校验公钥一致—–BEGIN PUBLIC KEY—–MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5EQJv0v0f1hrB7NIGwXihFmw+ugrJi7gnmOqaiYp7oFrzf4RSJ3DPr4K01F+CrjTdCPghDLq4fsKVxxHAjNjnbstbVlHZEV。

相关产品
云应用 产品页,含版本规格、价格与购买入口。

一、请求示例

POST http://localhost:8081/interfacesContent-Type: application/json{"Fields":{"aaa":1233,"BBBBB":"1212212"},"a111":"11111"}

二、1. 拼接规范请求串

按如下伪代码格式拼接规范请求串(CanonicalRequest):

CanonicalRequest = Algorithm + '\\n' + RequestTimestamp + '\\n' + HTTPRequestMethod + '\\n' + CanonicalURI + '\\n' + CanonicalQueryString + '\\n' + CanonicalHeaders + '\\n' + SignedHeaders + '\\n' + HashedRequestPayload

字段名称 描述
Algorithm 签名算法名称。目前仅支持 RSA-SHA256。
RequestTimestamp 请求时间戳,精确到秒级。从 Header 中获取:X-Cloudapp-Timestamp。
HTTPRequestMethod 请求方法,支持 POST、GET。以接口实际访问方式为准。
CanonicalQueryString 发起 HTTP 请求 URL 中的查询字符串,对于 POST 请求,固定为空字符串"";对于 GET 请求,则为 URL 中问号(?)后面的字符串内容,例如:Limit=10&Offset=0。注意:CanonicalQueryString 需要参考 RFC3986 进行 URLDecode 编码(特殊字符编码后需大写字母),字符集 UTF-8。推荐使用编程语言标准库进行编码。
CanonicalHeaders 参与签名的头部信息,至少包含 X-Cloudapp-Timestamp 和 X-Cloudapp-Host 两个头部,具体参与计算的 Header ,以 Header 中 X-Cloudapp-Signature-Headers 为准。示例代码:SignHeaderValue := []string{}for _, v := range SignedHeaders { SignHeaderValue = append(SignHeaderValue, fmt.Sprintf("%v=%v", strings.Trim(v, " "), strings.Trim(header.Get(v), " ")))}CanonicalHeaders := strings.Join(SignHeaderValue, "\\n")
SignedHeaders 参与签名的头部信息,说明此次请求有哪些头部参与了签名,和 CanonicalHeaders 包含的头部内容是一一对应的。示例:X-Cloudapp-Timestamp;X-Cloudapp-Host;content-type
HashedRequestPayload 请求正文的哈希值,计算伪代码为 Lowercase(HexEncode(Hash.SHA256(RequestPayload))),即对 HTTP 请求正文做 SHA256 哈希,然后十六进制编码,最后编码串转换成小写字母。对于 GET 请求,RequestPayload 固定为空字符串。此示例计算结果是:35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e89887f1398934f064。

根据以上规则,示例中得到的规范请求串如下:

HMAC-SHA2561762256838POST/interfacesX-Cloudapp-Timestamp=1762256838X-Cloudapp-Host=localhost:8081content-type=application/jsonX-Cloudapp-Timestamp;X-Cloudapp-Host;content-type56e18c53da8f844bb0394aea84de65396bd0b64514ae9b7818b214aee792768b

三、2. 验证签名

伪代码如下:

Signature = RsaSignVerify(PublicKey, HMAC_SHA256(CanonicalRequest), Base64Decode(Signature))

字段名称 描述
CanonicalRequest 签名字符串,第一步得到的值。
Signature 请求签名。从 header 的 X-Cloudapp-Signature 中获取到。
PublicKey 验签公钥。

四、3. 示例代码

package mainimport ( "context" "crypto" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/pem" "errors" "fmt" "net/http" "net/url" "reflect" "sort" "strings" "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go") const rsaPub = `—–BEGIN PUBLIC KEY—– MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvIPknZUDTxI8ep3wDFsN C2vF1sTUfF8f6pnjSduwtIc5yUYV/1hONRe4DwWRiXQPrDRTjlDridNRfglmktoe gUewNKfluGlxuTrUV35BBSGXdFTWJNg8/9j5zpsQS69mjwlh0wO8RxL0N9JatyHD HZBg9psp4RGj57wxEdyANv5IUvPQ0MUwuZ64UATl/0VI5eRM1FCJI5rE9kC+eJyH +c/63SNqBoSjG2kmXUb4nN8DPoDs90oA0wS2Yq1kr83kPAaFpcCIvnNKbXCK/hbY Jymt92Tcd8/viCxcEd88hacfzavWkyiLPl0W7Golnn2N9ZIyPwUb3a52yC4HiS5h 4XQSogiFluMQ+OIm4YwoaGgTILoU/Ip03LX7AILNI/Fcx9oGsLv2v4Lj01bStdJj 7EaCeitIw3SVyjlNAaoBTLzee0opBgVHGf8AnCzzf6qe7a0ics+pJbJi8+SGN6CF OBZxeQxqu7ZE9c6y05ZYQEh0e5V/5KlIZMG0FtmyMY1Q1l2CHjVJz4xzG8t8asqZ jg7uVpsnQhOxrbz68cAnw9X/9297K62VnECa8z9/3kSfY0SWd+lmc5HpFRzRJPt8 DLjFR/r/turTJ+HnvUe1aJzD2oa8D8Y09T6gQWmAlmqLOnt7aSPm/zN3rVt/6CPY 6EKSQMgJ7oOgKg4FybkNELcCAwEAAQ== —–END PUBLIC KEY—–`func main() { r := gin.Default() r.Any("/*action", func(ctx *gin.Context) { body, _ := ctx.GetRawData() signedHeader := strings.Split(ctx.Request.Header.Get("X-cloudapp-Signature-Headers"), ";") fmt.Printf("header: %v\\n", ctx.Request.Header) switch ctx.Request.Header.Get("X-Cloudapp-Algorithm") { case "RSA-SHA256": if err := RsaSha256SignVerify(ctx, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.URL.RawQuery, string(body), ctx.Request.Header, signedHeader, ctx.Request.Header.Get("X-Cloudapp-Timestamp"), ctx.Request.Header.Get("X-Cloudapp-Signature")); err != nil { ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, map[string]any{ "OK": false, "Algorithm": ctx.Request.Header.Get("X-Cloudapp-Algorithm"), }) default: ctx.JSON(http.StatusUnauthorized, gin.H{"error": "unsupported algorithm"}) } }) r.Run(":8081")}func RsaSha256SignVerify(ctx context.Context, Method string, path string, query string, body string, header http.Header, SignedHeaders []string, RequestTimestamp string, signed string) error { Algorithm := "RSA-SHA256" SignHeaderValue := []string{} for _, v := range SignedHeaders { SignHeaderValue = append(SignHeaderValue, fmt.Sprintf("%v=%v", strings.Trim(v, " "), strings.Trim(header.Get(v), " "))) } fmt.Printf("Body: %v, sha256: %x\\n", body, sha256.Sum256([]byte(body))) PayloadSha256 := fmt.Sprintf("%x", sha256.Sum256([]byte(body))) CanonicalRequest := Algorithm + "\\n" + // 签名方法 RequestTimestamp + "\\n" + // 请求时间戳,精确到秒 Method + "\\n" + // 请求方法 path + "\\n" + // 请求路径 query + "\\n" + // GET 请求参数 strings.Join(SignHeaderValue, "\\n") + "\\n" + // 需要签名的 Header 信息 strings.Join(SignedHeaders, ";") + "\\n" + // 签名的 Header 名称 PayloadSha256 // POST 签名 body 的 sha256 HashedCanonicalRequest := sha256.Sum256([]byte(CanonicalRequest)) fmt.Printf("sign plain text : %v. sha256: %x signture: %v", CanonicalRequest, HashedCanonicalRequest, signed) sign, _ := base64.StdEncoding.DecodeString(signed) publicKey, _ := LoadPubKey(ctx, rsaPub) // 使用 PKCS#1 v1.5 进行签名 if err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, HashedCanonicalRequest[:], sign); err != nil { return err } return nil}func SortParams(ctx context.Context, params map[string]any) map[string]any { newParams := make(map[string]any) keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { if reflect.TypeOf(params[k]).Kind() == reflect.Map { newParams[k] = SortParams(ctx, params[k].(map[string]any)) } else { newParams[k] = params[k] } newParams[k] = params[k] } return newParams}func ParamsToQuery(params map[string]any) string { query := url.Values{} for k, v := range params { if reflect.TypeOf(v).Kind() == reflect.Map { js, _ := jsoniter.Marshal(v) query.Add(k, string(js)) } else { query.Add(k, fmt.Sprintf("%v", v)) } } return query.Encode()}func LoadPubKey(ctx context.Context, pubKey string) (*rsa.PublicKey, error) { block, _ := pem.Decode([]byte(rsaPub)) if block == nil { return nil, errors.New("failed to decode PEM block containing private key") } key, parseErr := x509.ParsePKIXPublicKey(block.Bytes) if parseErr != nil { return nil, parseErr } var ok bool publicKey, ok := key.(*rsa.PublicKey) if !ok { return nil, errors.New("not an RSA private key") } return publicKey, nil}

赞 (0)
上一篇 4天前
下一篇 4天前