pageExecute()返回的URL打开后提示invalid-signature
结论
invalid-signature 表示支付宝无法使用当前应用配置的“应用公钥”验证请求签名。常见原因主要有两种:
- 服务端签名所用的应用私钥,与支付宝开放平台中该
app_id配置的应用公钥不属于同一密钥对。 pageExecute()生成 URL 后,前端、网关或模板引擎又对 URL 或参数进行了编码、解码或重新拼接,导致验签内容发生变化。
先确认密钥、app_id 和运行环境完全对应,再让浏览器直接跳转到 SDK 生成的原始 URL。不要对整个 URL 再执行 URLEncoder.encode(),也不要拆分、排序或重新拼装其中的参数。
一、检查密钥配置是否匹配
支付宝验证请求签名时,使用的是开放平台中配置的“应用公钥”;服务端生成签名时,使用的是对应的“应用私钥”。两者必须属于同一密钥对。
逐项检查以下配置:
- 当前请求中的
app_id是否确实为2021002190618823。 - 服务端加载的私钥是否属于该应用,而非其他应用或沙箱环境。
- 支付宝开放平台中配置的是否为“应用公钥”,不要误填“支付宝公钥”。
- SDK 配置中的
alipayPublicKey是支付宝公钥,供商户验证支付宝响应,不能代替应用公钥。 sign_type是否与实际签名算法一致。请求使用RSA2时,应采用 SHA256withRSA。- 修改应用密钥后,部署环境是否仍在读取旧配置。
- 多实例部署时,各实例加载的密钥是否一致。
可以从私钥导出公钥,再与开放平台中的应用公钥进行比较。例如使用 OpenSSL:
openssl rsa -in app_private_key.pem -pubout -out derived_public_key.pem
比较时忽略 PEM 头尾、空格和换行,只检查 Base64 主体。如果两者不同,需要把正确的应用公钥重新配置到对应应用,或者改用与平台现有应用公钥匹配的私钥。
不要把应用私钥粘贴到日志、工单或前端页面中。
二、不要修改 pageExecute() 生成的 URL
SDK 已经处理了参数排序、签名和 URL 编码。生成 URL 后再做处理,很容易破坏签名。
错误示例:
String payUrl = alipayClient.pageExecute(request, "GET");
String encodedUrl = URLEncoder.encode(payUrl, StandardCharsets.UTF_8);
return encodedUrl;
也不要拆解 URL 中的参数再重新拼接:
// 不建议:重新解析、排序、编码或拼装 pageExecute() 的结果
Map<String, String> params = parseQuery(payUrl);
String rebuiltUrl = rebuildUrl(params);
建议直接重定向:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setNotifyUrl("https://landscaperoad.cn/hwgateway/api-honghu-pay/pay/alipay_notify");
request.setBizContent(bizContent);
String payUrl = alipayClient.pageExecute(request, "GET");
response.sendRedirect(payUrl);
如果必须把 URL 返回给前端,可以将其放入 JSON 字段,由前端直接跳转:
Map<String, String> result = new HashMap<>();
result.put("payUrl", alipayClient.pageExecute(request, "GET"));
return result;
前端直接使用:
window.location.href = response.payUrl;
不要再次调用:
window.location.href = encodeURIComponent(response.payUrl);
三、检查 & 和 " 是否进入了实际请求
问题中展示的验签字符串包含:
&
"
如果这只是错误页面显示时产生的 HTML 转义,通常不会影响请求。页面上显示的是 &,底层原始内容仍可能是 &。
如果实际请求 URL 中确实存在字面量 &,参数就会被破坏。例如:
app_id=2021002190618823&biz_content=...
正确的查询参数分隔符应为:
app_id=2021002190618823&biz_content=...
同样,biz_content 应传入正常的 JSON,再由 SDK 进行 URL 编码。不要提前将其替换成 HTML 实体:
{"body":"1232022051116365125","out_trade_no":"4431652262692070"}
调用 SDK 前不应变成:
{"body":"1232022051116365125"}
可以在浏览器开发者工具的 Network 面板中查看最终请求地址,确认实际发送的是 %22、%26 等 URL 编码,还是错误的 %26amp%3B、& 等 HTML 实体。
四、检查 + 是否被转换为空格
RSA2 签名经过 Base64 编码后,可能包含 +、/、=。如果中间系统先解码查询参数,再用错误的方式重新编码,+ 可能被当成空格,最终导致签名失效。
需要排查:
- 前端是否调用
decodeURIComponent()后重新拼接 URL。 - API 网关是否修改了查询参数。
- 重定向接口是否先解析
sign,再生成新的 URL。 - 模板引擎是否对 URL 进行了 HTML 转义。
- 日志中显示的空格是否原本为
+。
建议记录并对比以下内容:
pageExecute()刚返回时的 URL。- 后端实际返回给前端的 URL。
- 浏览器最终发往支付宝的 URL。
记录时可以对 sign 脱敏,但不要改动其他参数,这样才能判断请求是在哪一层被修改的。
五、核对 SDK 初始化参数
普通公钥模式下,初始化方式可以参考:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
检查以下内容:
- 正式应用使用正式网关,沙箱应用使用沙箱网关。
appId、应用私钥和应用公钥属于同一环境。appPrivateKey是应用私钥。alipayPublicKey是支付宝公钥。- 字符集统一为
UTF-8。 - 签名算法统一为
RSA2。
如果应用使用证书模式,应采用对应的证书客户端配置,不能混用普通公钥模式的初始化方式。具体构造方式取决于当前支付宝 Java SDK 版本和应用采用的密钥模式。
六、使用 SDK 构造业务参数
尽量让 SDK 处理签名和编码,不要手动生成签名字符串:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setNotifyUrl(
"https://landscaperoad.cn/hwgateway/api-honghu-pay/pay/alipay_notify"
);
request.setBizContent("""
{
"out_trade_no": "4431652262692070",
"total_amount": "10.01",
"subject": "【明星徐百慧推荐】摩洛哥精油免蒸染烫修复干枯补水顺滑深层护理滋养官方正品发膜",
"body": "1232022051116365125",
"product_code": "QUICK_MSECURITY_PAY",
"timeout_express": "300m",
"passback_params": "443_1232022051116365125_123_goods_alipay_app"
}
""");
String payUrl = alipayClient.pageExecute(request, "GET");
如果当前 Java 版本不支持文本块,可以使用 JSON 序列化库生成 biz_content。不要自行拼接包含中文、引号和特殊字符的 JSON。
推荐排查顺序
- 确认
app_id、应用私钥和开放平台应用公钥属于同一应用、同一环境。 - 确认没有混用普通公钥模式和证书模式。
- 在
pageExecute()返回后,立即保存一份脱敏 URL。 - 将其与浏览器最终请求的 URL 对比,检查中间是否发生了重新编码或拼接。
- 检查实际 URL 中是否出现
&、",以及sign中原本的+是否变成了空格。 - 暂时绕过前端传递,由后端直接调用
sendRedirect()。如果此时可以正常打开,问题基本出在前端或中间网关处理 URL 的过程中。 - 如果直接访问原始 URL 仍然报错,优先重新核对密钥对和 SDK 初始化配置。
仅凭给出的验签字符串,还不能判断问题究竟是密钥不匹配,还是 URL 被修改。这两项最常见,也最容易验证。通过“后端直接重定向测试”和“从私钥导出公钥进行对比”,通常可以很快缩小排查范围。
备注:内容仅供参考。
