From 752e43f9297a5043946b3cd8e5222bf0a54d55e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 02:31:36 +0000 Subject: [PATCH 1/3] Initial plan From 295a0f32715bee3a30064ef2377da178f5f74f21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 02:38:45 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=8C=E5=9F=8E?= =?UTF-8?q?=E9=85=8D=E9=80=81=E6=8E=A5=E5=8F=A3RSA=E7=AD=BE=E5=90=8Dpayloa?= =?UTF-8?q?d=E6=A0=BC=E5=BC=8F=E9=94=99=E8=AF=AF=EF=BC=88=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=8140234=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据微信官方API签名文档,待签名串格式应为: urlpath\nappid\ntimestamp\npostdata 原代码错误地在payload中包含了rsaKeySn,导致签名验证失败(40234错误)。 修复:从postWithSignature方法的签名payload中移除rsaKeySn字段。 新增:WxMaSignaturePayloadTest单元测试验证签名格式正确性。 Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com> --- .../miniapp/api/impl/BaseWxMaServiceImpl.java | 4 +- .../api/impl/WxMaSignaturePayloadTest.java | 112 ++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java index 47ce08bef..6db89aa9a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java @@ -962,8 +962,8 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro reqData.addProperty("authtag", base64Encode(authTag)); String requestJson = reqData.toString(); - // 计算签名 RSA - String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + rsaKeySn + "\n" + requestJson; + // 计算签名 RSA,待签名串格式:urlpath\nappid\ntimestamp\npostdata + String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson; byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8); RSAPrivateKey priKey; try { diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java new file mode 100644 index 000000000..5c7531b0b --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java @@ -0,0 +1,112 @@ +package cn.binarywang.wx.miniapp.api.impl; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Signature; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; +import java.util.Base64; +import org.testng.annotations.Test; + +/** + * 验证同城配送 API 签名 payload 格式的单元测试。 + * + *

根据微信官方文档,待签名串格式为:
+ * {@code urlpath\nappid\ntimestamp\npostdata}
+ * 字段之间使用换行符 {@code \n} 分隔,末尾无额外回车符。 + * + * @author GitHub Copilot + * @see + * 微信服务端API签名指南 + */ +public class WxMaSignaturePayloadTest { + + /** + * 验证正确的签名 payload 格式(不含 rsaKeySn)可以通过签名验证, + * 即格式为:urlpath\nappid\ntimestamp\npostdata + */ + @Test + public void testCorrectSignaturePayloadFormat() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + + String urlPath = "https://api.weixin.qq.com/cgi-bin/express/intracity/createstore"; + String appId = "wx1234567890abcdef"; + long timestamp = 1700000000L; + String requestJson = "{\"iv\":\"abc\",\"data\":\"xyz\",\"authtag\":\"tag\"}"; + + // 正确格式:urlpath\nappid\ntimestamp\npostdata(不含 rsaKeySn) + String correctPayload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson; + byte[] dataBuffer = correctPayload.getBytes(StandardCharsets.UTF_8); + + Signature signer = Signature.getInstance("RSASSA-PSS"); + PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); + signer.setParameter(pssSpec); + signer.initSign(privateKey); + signer.update(dataBuffer); + byte[] sigBytes = signer.sign(); + String signatureStr = Base64.getEncoder().encodeToString(sigBytes); + + // 使用公钥验证签名 + Signature verifier = Signature.getInstance("RSASSA-PSS"); + verifier.setParameter(pssSpec); + verifier.initVerify(publicKey); + verifier.update(dataBuffer); + assertTrue(verifier.verify(Base64.getDecoder().decode(signatureStr)), + "正确格式的签名应该能通过验证"); + } + + /** + * 验证错误的签名 payload(含 rsaKeySn)签名后,用正确 payload 验证会失败。 + * 这证明了原来代码中将 rsaKeySn 加入 payload 是错误的。 + */ + @Test + public void testIncorrectPayloadWithRsaKeySnFails() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + + String urlPath = "https://api.weixin.qq.com/cgi-bin/express/intracity/createstore"; + String appId = "wx1234567890abcdef"; + long timestamp = 1700000000L; + String rsaKeySn = "some_serial_number"; + String requestJson = "{\"iv\":\"abc\",\"data\":\"xyz\",\"authtag\":\"tag\"}"; + + // 错误格式:payload 中包含了 rsaKeySn(修复前的代码逻辑) + String incorrectPayload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + rsaKeySn + "\n" + requestJson; + byte[] incorrectData = incorrectPayload.getBytes(StandardCharsets.UTF_8); + + Signature signer = Signature.getInstance("RSASSA-PSS"); + PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); + signer.setParameter(pssSpec); + signer.initSign(privateKey); + signer.update(incorrectData); + byte[] sigBytes = signer.sign(); + String signatureStr = Base64.getEncoder().encodeToString(sigBytes); + + // 用正确格式的 payload 去验证签名,应该失败 + String correctPayload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson; + byte[] correctData = correctPayload.getBytes(StandardCharsets.UTF_8); + + Signature verifier = Signature.getInstance("RSASSA-PSS"); + verifier.setParameter(pssSpec); + verifier.initVerify(publicKey); + verifier.update(correctData); + + boolean verified = verifier.verify(Base64.getDecoder().decode(signatureStr)); + assertFalse(verified, "用错误 payload 生成的签名不应该通过正确 payload 的验证," + + "说明 rsaKeySn 不应该包含在签名 payload 中"); + } +} From 2ad43b3646299452e5b835b33303d979502f84b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 03:20:01 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=B0=86?= =?UTF-8?q?=E7=AD=BE=E5=90=8Dpayload=E6=9E=84=E9=80=A0=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E6=8A=BD=E5=8F=96=E4=B8=BA=E5=8F=AF=E6=B5=8B=E8=AF=95=E7=9A=84?= =?UTF-8?q?=E5=8C=85=E5=8F=AF=E8=A7=81=E6=96=B9=E6=B3=95=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=B5=8B=E8=AF=95=E6=97=A0=E6=B3=95=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E5=9B=9E=E5=BD=92=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 postWithSignature 中的 payload 拼接逻辑提取为包可见静态方法 buildSignaturePayload - 重写 WxMaSignaturePayloadTest,直接测试生产代码的 payload 格式 - 新测试断言:恰好4个字段、不含rsaKeySn、格式与官方规范完全一致 - 如果将来有人误将 rsaKeySn 加回 payload,测试将立即失败 Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com> --- .../miniapp/api/impl/BaseWxMaServiceImpl.java | 19 ++- .../api/impl/WxMaSignaturePayloadTest.java | 119 ++++++------------ 2 files changed, 57 insertions(+), 81 deletions(-) diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java index 6db89aa9a..13b4248e2 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java @@ -908,6 +908,23 @@ private String base64Encode(byte[] data) { return Base64.getEncoder().encodeToString(data); } + /** + * 构造 RSA 待签名串。 + * + *

根据微信官方 API 签名规范,待签名串格式为:
+ * {@code urlpath\nappid\ntimestamp\npostdata}
+ * 字段之间使用换行符 {@code \n} 分隔,共 4 个字段,末尾无额外回车符。 + * + * @param urlPath 当前请求 API 的 URL,不含 Query 参数 + * @param appId 小程序 AppId + * @param timestamp 签名时的时间戳 + * @param postData 加密后的请求 POST 数据(JSON 字符串) + * @return 拼接好的待签名串 + */ + static String buildSignaturePayload(String urlPath, String appId, long timestamp, String postData) { + return urlPath + "\n" + appId + "\n" + timestamp + "\n" + postData; + } + @Override public String postWithSignature(String url, JsonObject jsonObject) throws WxErrorException { long timestamp = System.currentTimeMillis() / 1000; @@ -963,7 +980,7 @@ public String postWithSignature(String url, JsonObject jsonObject) throws WxErro String requestJson = reqData.toString(); // 计算签名 RSA,待签名串格式:urlpath\nappid\ntimestamp\npostdata - String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson; + String payload = buildSignaturePayload(urlPath, appId, timestamp, requestJson); byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8); RSAPrivateKey priKey; try { diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java index 5c7531b0b..82f436fe5 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSignaturePayloadTest.java @@ -1,25 +1,17 @@ package cn.binarywang.wx.miniapp.api.impl; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import java.nio.charset.StandardCharsets; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.Signature; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.MGF1ParameterSpec; -import java.security.spec.PSSParameterSpec; -import java.util.Base64; import org.testng.annotations.Test; /** * 验证同城配送 API 签名 payload 格式的单元测试。 * - *

根据微信官方文档,待签名串格式为:
+ *

直接测试 {@link BaseWxMaServiceImpl#buildSignaturePayload} 生产方法, + * 确保待签名串格式符合微信官方规范:
* {@code urlpath\nappid\ntimestamp\npostdata}
- * 字段之间使用换行符 {@code \n} 分隔,末尾无额外回车符。 + * 共 4 个字段,字段间以换行符 {@code \n} 分隔,末尾无额外回车符。 * * @author GitHub Copilot * @see