Jelajahi Sumber

fix: 签名验证同时尝试appsecret和ticket两种方式,日志标注匹配来源

SDK文档和代码对验签方式描述不一致(API文档用appsecret,
CallbackUtils用ticket),实际以哪种为准需观察日志确认。
部署后查看签名验证通过的日志标注来判断正确方式。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 minggu lalu
induk
melakukan
c0d30dabae

+ 37 - 36
haha-service/src/main/java/com/haha/service/impl/HahaCallbackServiceImpl.java

@@ -34,7 +34,7 @@ import com.haha.service.payment.payscore.PayScoreService;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -538,6 +538,9 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
         }
     }
 
+    @Value("${haha.api.app-secret}")
+    private String appSecret;
+
     @Override
     public boolean validateSign(Map<String, Object> params) {
         String receivedSign = extractParam(params, "sign");
@@ -546,47 +549,45 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
             return false;
         }
 
-        // 回调验签使用 ticket(从 /token/get 接口获取),而非 appSecret
-        String ticket;
-        try {
-            // 触发 token 获取/刷新,同时会填充 ticket
-            hahaClient.getAccessToken();
-            ticket = hahaClient.getTicket();
-        } catch (Exception e) {
-            log.error("获取验签 ticket 失败", e);
-            return false;
-        }
+        String signContent = buildSignContent(params);
 
-        if (ticket == null || ticket.isEmpty()) {
-            log.error("ticket 为空,无法验证签名");
-            return false;
+        // 诊断模式:同时尝试 appsecret 和 ticket 两种方式,日志中明确标注匹配情况
+        // 待确认哪种方式正确后,去掉不需要的分支
+        boolean matched = false;
+
+        // 方式1:末尾拼接 &appsecret=<appSecret>(哈哈API文档描述的方式)
+        if (appSecret != null && !appSecret.isEmpty()) {
+            String sign1 = md5(signContent + "&appsecret=" + appSecret);
+            if (sign1.equalsIgnoreCase(receivedSign)) {
+                log.debug("签名验证通过 (appsecret方式)");
+                matched = true;
+            }
         }
 
-        try {
-            // 哈哈平台回调签名算法:
-            // 1. 排除 sign 字段,按 key 字典序排列
-            // 2. 拼接为 key1=value1&key2=value2&...
-            // 3. 末尾拼接 &ticket=<ticket>
-            // 4. MD5 得到签名值
-            // 参考: haha-sdk CallbackUtils.verifySign()
-
-            String signContent = buildSignContent(params);
-            String signStr = signContent + "&ticket=" + ticket;
-            String computedSign = md5(signStr);
-
-            boolean isValid = computedSign.equalsIgnoreCase(receivedSign);
-            if (!isValid) {
-                log.warn("签名验证失败 - 计算签名: {}, 接收签名: {}, 签名内容: {}",
-                        computedSign, receivedSign, signContent);
-            } else {
-                log.debug("签名验证通过");
+        // 方式2:末尾拼接 &ticket=<ticket>(SDK CallbackUtils 描述的方式)
+        if (!matched) {
+            String ticket = null;
+            try {
+                hahaClient.getAccessToken();
+                ticket = hahaClient.getTicket();
+            } catch (Exception e) {
+                log.debug("获取 ticket 失败: {}", e.getMessage());
+            }
+            if (ticket != null && !ticket.isEmpty()) {
+                String sign2 = md5(signContent + "&ticket=" + ticket);
+                if (sign2.equalsIgnoreCase(receivedSign)) {
+                    log.debug("签名验证通过 (ticket方式)");
+                    matched = true;
+                }
             }
-            return isValid;
+        }
 
-        } catch (Exception e) {
-            log.error("签名验证异常", e);
-            return false;
+        if (!matched) {
+            log.warn("签名验证失败(两种方式均不匹配) - 接收签名: {}, 签名内容: {}",
+                    receivedSign, signContent);
         }
+
+        return matched;
     }
 
     /**