refactor(openapi): 客户端签名统一为字典序+&拼接风格
- BuildOpenV1Sign 改为 app_key/body_sha256/method/nonce/path/timestamp 按 ASCII 字典序用 & 拼接,与上游 BuildSignString 风格一致 - body 以 SHA256 摘要参与签名,避免大 body 与特殊字符问题 - 同步更新中间件调用点、测试用例与前端鉴权文档
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user