diff --git a/backend/internal/middleware/open_auth.go b/backend/internal/middleware/open_auth.go index ff5f838..3d3fc74 100644 --- a/backend/internal/middleware/open_auth.go +++ b/backend/internal/middleware/open_auth.go @@ -35,7 +35,8 @@ type OpenAuthConfig struct { } // OpenAuth 校验独立 API 客户端、时间戳、持久化 nonce 与 HMAC 签名。 -// 客户侧新接口只使用 X-App-Key 和 body SHA256;上游发货接口独立使用 SourceOpenAuth。 +// 客户侧接口使用 X-App-Key + 字典序 & 拼接签名(app_key/body_sha256/method/nonce/path/timestamp); +// 上游发货接口独立使用 SourceOpenAuth(api_key + 原始 body)。 func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc { if cfg.SkewSeconds <= 0 { cfg.SkewSeconds = 300 @@ -100,7 +101,7 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc { method := strings.ToUpper(c.Request.Method) path := c.Request.URL.Path - expected := BuildOpenV1Sign(secret, timestamp, nonce, method, path, bodyBytes) + expected := BuildOpenV1Sign(secret, appKey, timestamp, nonce, method, path, bodyBytes) if !hmac.Equal([]byte(strings.ToLower(sign)), []byte(expected)) { response.Unauthorized(c, "签名校验失败") c.Abort() @@ -164,20 +165,23 @@ func GetMerchantID(c *gin.Context) uint { return merchantID } -// BuildOpenV1Sign 生成新开放接口签名:timestamp、nonce、method、path 与 body SHA256。 -func BuildOpenV1Sign(secret, timestamp, nonce, method, path string, body []byte) string { +// BuildOpenV1Sign 生成客户侧开放接口签名:参数按 ASCII 字典序 + "&" 拼接, +// 与上游 BuildSignString 风格一致;body 以 SHA256 摘要参与签名(避免大 body 与特殊字符问题)。 +// 参与签名的参数固定顺序为:app_key, body_sha256, method, nonce, path, timestamp。 +func BuildOpenV1Sign(secret, appKey, timestamp, nonce, method, path string, body []byte) string { bodyHash := sha256.Sum256(body) content := strings.Join([]string{ - timestamp, - nonce, - strings.ToUpper(method), - path, - hex.EncodeToString(bodyHash[:]), - }, "\n") + "app_key=" + appKey, + "body_sha256=" + hex.EncodeToString(bodyHash[:]), + "method=" + strings.ToUpper(method), + "nonce=" + nonce, + "path=" + path, + "timestamp=" + timestamp, + }, "&") return hmacSHA256Hex(secret, content) } -// BuildSignString 保留旧接口的字典序签名算法,供兼容客户端和测试使用。 +// BuildSignString 保留旧接口的字典序签名算法,供上游发货兼容客户端和测试使用。 func BuildSignString(apiKey, timestamp, nonce, method, path, body string) string { return strings.Join([]string{ "api_key=" + apiKey, diff --git a/backend/internal/middleware/open_auth_test.go b/backend/internal/middleware/open_auth_test.go index e73644f..9f58562 100644 --- a/backend/internal/middleware/open_auth_test.go +++ b/backend/internal/middleware/open_auth_test.go @@ -66,7 +66,7 @@ func TestOpenAuthV1AcceptsSignedRequestAndRejectsReplay(t *testing.T) { ts := strconv.FormatInt(time.Now().Unix(), 10) nonce := "nonce-123456" path := "/api/client/v1/orders" - sign := BuildOpenV1Sign(secret, ts, nonce, http.MethodPost, path, []byte(body)) + sign := BuildOpenV1Sign(secret, appKey, ts, nonce, http.MethodPost, path, []byte(body)) req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body)) req.Header.Set("X-App-Key", appKey) diff --git a/frontend/src/pages/OpenApiDocs.tsx b/frontend/src/pages/OpenApiDocs.tsx index 061ee04..ab9253e 100644 --- a/frontend/src/pages/OpenApiDocs.tsx +++ b/frontend/src/pages/OpenApiDocs.tsx @@ -125,33 +125,32 @@ function AuthTab() { - - {'timestamp\nnonce\nMETHOD\npath\nsha256(body)'} + + app_keybody_sha256methodnoncepathtimestamp - 按固定顺序,用换行符 \n 拼接 - 大写,如 GET / POST + 参数按 ASCII 字典序,用 & 拼成 k1=v1&k2=v2&... + + app_key, body_sha256, method, nonce, path, timestamp + + 大写,如 GET / POST 仅 URL.Path,不含域名和 query - GET 为空字节;POST 必须与实际发送 body 完全一致 + + SHA256(原始 body 字节) 的十六进制小写;GET 用空 body 的摘要 + + 原样拼接,不做 URL encode - hex( HMAC-SHA256( app_secret, 签名内容 ) ),小写十六进制 + hex( HMAC-SHA256( app_secret, 签名串 ) ),小写十六进制 -
{`timestamp
-nonce
-GET
-/api/client/v1/products
-sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`}
+
{`app_key=ak_xxx&body_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&method=GET&nonce=a1b2c3d4e5f67890&path=/api/client/v1/products×tamp=1721450000`}
-
{`timestamp
-nonce
-POST
-/api/client/v1/orders
-sha256(body) = <实际请求 body 字节的 SHA256 十六进制>`}
+
{`# body = {"client_order_no":"shop-10001","sku":"suit_pink_sheep"}
+app_key=ak_xxx&body_sha256=<实际请求 body 字节的 SHA256 十六进制>&method=POST&nonce=a1b2c3d4e5f67890&path=/api/client/v1/orders×tamp=1721450000`}
`} description={ }