Эх сурвалжийг харах

fix: 回调签名验证改用ticket而非appSecret

哈哈平台回调通知使用 ticket 验签(从 /token/get 接口获取),而非 appSecret。
- HahaClient: 保存 token 响应中的 ticket 字段
- HahaCallbackServiceImpl: 验签时拼接 &ticket=<ticket> 后进行 MD5

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 долоо хоног өмнө
parent
commit
f885191e41

+ 8 - 2
haha-sdk/src/main/java/com/haha/sdk/HahaClient.java

@@ -37,7 +37,12 @@ public class HahaClient {
      * 访问令牌
      */
     private String accessToken;
-    
+
+    /**
+     * 回调验签票据(从 /token/get 接口返回,用于验证哈哈平台的回调通知签名)
+     */
+    private String ticket;
+
     /**
      * 令牌过期时间(时间戳,毫秒)
      */
@@ -124,7 +129,8 @@ public class HahaClient {
             @SuppressWarnings("unchecked")
             Map<String, Object> data = (Map<String, Object>) result.get("data");
             this.accessToken = (String) data.get("access_token");
-            
+            this.ticket = (String) data.get("ticket");
+
             // 计算过期时间,使用文档中的exprire字段(注意拼写)
             Integer expire = (Integer) data.get("exprire");
             if (expire != null) {

+ 21 - 15
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;
 
@@ -70,9 +70,6 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
     @Autowired(required = false)
     private PayScoreService payScoreService;
 
-    @Value("${haha.api.app-secret}")
-    private String appSecret;
-
     @Autowired
     private com.haha.common.config.CommonConfig commonConfig;
 
@@ -549,23 +546,32 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
             return false;
         }
 
-        if (appSecret == null || appSecret.isEmpty()) {
-            log.error("appSecret 未配置,无法验证签名");
+        // 回调验签使用 ticket(从 /token/get 接口获取),而非 appSecret
+        String ticket;
+        try {
+            // 触发 token 获取/刷新,同时会填充 ticket
+            hahaClient.getAccessToken();
+            ticket = hahaClient.getTicket();
+        } catch (Exception e) {
+            log.error("获取验签 ticket 失败", e);
+            return false;
+        }
+
+        if (ticket == null || ticket.isEmpty()) {
+            log.error("ticket 为空,无法验证签名");
             return false;
         }
 
         try {
-            // 哈哈平台签名验证算法:
-            // 1. 排除 sign、sign_type 等签名相关字段
-            // 2. 剩余参数按 key 字典序升序排列
-            // 3. 拼接为 key1=value1&key2=value2&...&keyN=valueN 格式
-            // 4. 末尾拼接 &appsecret=<secret>
-            // 5. 对拼接后的字符串进行 MD5,得到签名值
-            // 参考: haha-sdk SignUtils.generateSign()
+            // 哈哈平台回调签名算法:
+            // 1. 排除 sign 字段,按 key 字典序排列
+            // 2. 拼接为 key1=value1&key2=value2&...
+            // 3. 末尾拼接 &ticket=<ticket>
+            // 4. MD5 得到签名值
+            // 参考: haha-sdk CallbackUtils.verifySign()
 
             String signContent = buildSignContent(params);
-            // 哈哈平台签名规则: 在参数字符串末尾拼接 &appsecret=<secret>,然后 MD5
-            String signStr = signContent + "&appsecret=" + appSecret;
+            String signStr = signContent + "&ticket=" + ticket;
             String computedSign = md5(signStr);
 
             boolean isValid = computedSign.equalsIgnoreCase(receivedSign);