diff --git a/firstleg/src/main/java/com/baiyue/firstleg/lingxing/LingxingDirectClient.java b/firstleg/src/main/java/com/baiyue/firstleg/lingxing/LingxingDirectClient.java index b3b81ca06..a6043dd01 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/lingxing/LingxingDirectClient.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/lingxing/LingxingDirectClient.java @@ -87,15 +87,18 @@ public class LingxingDirectClient { } private static String text(JsonNode node, String key) { return node.path(key).isMissingNode() ? null : node.path(key).asText(null); } private static java.math.BigDecimal decimal(JsonNode node, String key) { return node.path(key).isNumber() ? node.path(key).decimalValue() : null; } - private String trackKey(String method, String json) { - if (properties.getTrackKeyV2SecretBase64() == null || properties.getTrackKeyV2SecretBase64().isBlank()) throw new IllegalStateException("未配置领星 Track-Key v2 密钥"); + static String buildTrackKey(String method, String json, String secretBase64) { + if (secretBase64 == null || secretBase64.isBlank()) throw new IllegalStateException("未配置领星 Track-Key v2 密钥"); try { byte[] digest = MessageDigest.getInstance("SHA-256").digest(json.getBytes(StandardCharsets.UTF_8)); StringBuilder hex = new StringBuilder(); for (byte b : digest) hex.append(String.format(Locale.ROOT, "%02x", b)); - Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(Base64.getDecoder().decode(properties.getTrackKeyV2SecretBase64()), "HmacSHA256")); + byte[] key = Base64.getDecoder().decode(secretBase64); + if (key.length != 44) throw new IllegalStateException("领星 Track-Key v2 密钥解码后必须是 44 字节"); + Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); return "v2:" + Base64.getEncoder().encodeToString(mac.doFinal((method.toUpperCase(Locale.ROOT) + hex).getBytes(StandardCharsets.UTF_8))); } catch (Exception ex) { throw new IllegalStateException("领星 Track-Key 生成失败", ex); } } + private String trackKey(String method, String json) { return buildTrackKey(method, json, properties.getTrackKeyV2SecretBase64()); } private String canonical(JsonNode node) throws Exception { return mapper.writeValueAsString(sort(node)); } private JsonNode sort(JsonNode node) { if (!node.isObject()) return node; diff --git a/firstleg/src/test/java/com/baiyue/firstleg/lingxing/LingxingTrackKeyVectorTest.java b/firstleg/src/test/java/com/baiyue/firstleg/lingxing/LingxingTrackKeyVectorTest.java new file mode 100644 index 000000000..a06f80fad --- /dev/null +++ b/firstleg/src/test/java/com/baiyue/firstleg/lingxing/LingxingTrackKeyVectorTest.java @@ -0,0 +1,18 @@ +package com.baiyue.firstleg.lingxing; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +class LingxingTrackKeyVectorTest { + @Test + void matchesNodeTrackKeyV2VectorWhenSecretIsProvided() { + String secret = System.getenv("LINGXING_TRACK_KEY_V2_SECRET_BASE64"); + assumeTrue(secret != null && !secret.isBlank(), + "Set LINGXING_TRACK_KEY_V2_SECRET_BASE64 to run the protected vector test"); + String body = "{\"businessType\":\"oms\",\"deviceFingerprint\":\"12345678\",\"deviceInfo\":\"UA | 1920x1080 | Asia/Shanghai\",\"loginAccount\":\"demo\",\"loginFlowId\":\"00112233445566778899aabbccddeeff\",\"password\":\"secret\"}"; + assertThat(LingxingDirectClient.buildTrackKey("POST", body, secret)) + .isEqualTo("v2:IbVx50WEXAxeGsc/66LjqC0WSxT2ZF0K4V4JYU/LkYw="); + } +}