支付宝支付结果通知调用rsaCheckV1验签失败
结论
支付宝异步通知中的 sign 是支付宝用私钥生成的数字签名。商户验签时,应使用与当前环境、当前应用配置对应的支付宝公钥。支付宝私钥只由支付宝持有,不会提供给商户。
如果 rsaCheckV1 始终返回 false,通常需要排查以下问题:
- 传入的
params已被修改,不再是原始通知参数。 - 将
request.getParameterMap()直接强制转换成了Map<String, String>。 - 使用的支付宝公钥与当前环境或应用不匹配。
- 应用使用证书模式,验签时却采用了普通公钥模式。
charset、sign_type或支付宝 SDK 版本与实际通知不匹配。- 参数在验签前经过二次解码、重新编码、裁剪或类型转换。
正确理解签名机制
支付宝生成通知签名的过程可以简化为:
支付宝私钥 + 待签名字符串 → sign
商户收到通知后,按以下方式验签:
支付宝公钥 + 待验签字符串 + sign → true / false
sign 是数字签名,并不是用支付宝私钥加密后的通知内容。支付宝私钥不会下发给商户。商户使用支付宝公钥,可以确认通知是否由支付宝签发,以及参数在传输过程中是否被篡改。
以下几类密钥不要混用:
- 应用私钥:由商户持有,用于签署商户发出的请求。
- 应用公钥:通常提交给支付宝,用于验证商户的请求签名。
- 支付宝私钥:由支付宝持有,用于签署响应和异步通知。
- 支付宝公钥:由商户使用,用于验证支付宝的响应和通知。
首先检查参数 Map 的构造方式
Servlet 的 request.getParameterMap() 通常返回 Map<String, String[]>,不能仅靠强制类型转换把它当成 Map<String, String>:
(Map<String, String>) request.getParameterMap()
强制转换不会把 String[] 变成 String,因此可能导致待验签字符串错误,甚至在运行时触发类型转换异常。应该逐项构造一个新的参数 Map:
import com.alipay.api.internal.util.AlipaySignature;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
public boolean verifyAlipayNotify(
HttpServletRequest request,
String alipayPublicKey) throws Exception {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
String[] values = entry.getValue();
if (values == null || values.length == 0) {
params.put(entry.getKey(), "");
} else {
params.put(entry.getKey(), String.join(",", values));
}
}
String signType = params.get("sign_type");
if (signType == null || signType.isEmpty()) {
signType = "RSA2";
}
return AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
signType
);
}
如果项目仍在使用 javax.servlet,只需修改示例中的导入:
import javax.servlet.http.HttpServletRequest;
rsaCheckV1 会按照支付宝 SDK 的规则组织待验签内容。除非是在定位问题,并且完全遵循对应 SDK 的签名规则,否则不要自行删除、拼接或排序参数。
核对支付宝公钥
即使已经确认使用的是支付宝公钥,也要继续检查该公钥是否与当前通知匹配:
- 生产环境的通知必须使用生产环境对应的支付宝公钥。
- 沙箱通知必须使用沙箱环境对应的支付宝公钥。
- 不同应用的密钥配置不能混用。
- 重新生成密钥或修改配置后,需要确认程序实际加载的是当前有效配置。
- 不要把应用公钥、应用公钥证书或支付宝公钥证书误当成普通支付宝公钥。
使用普通公钥模式时,传入 rsaCheckV1 的通常是支付宝 RSA 公钥正文,例如:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
是否需要包含 -----BEGIN PUBLIC KEY----- 和换行,取决于 SDK 版本及密钥加载方式。建议使用支付宝开放平台提供的 SDK,并按照该 SDK 的要求保存公钥。
证书模式不能混用普通公钥模式
如果应用接入的是公钥证书模式,验签时应加载支付宝公钥证书,不能把证书文件内容直接作为普通公钥传给 rsaCheckV1。
不同版本的支付宝 Java SDK 可能提供不同的证书验签方法,常见写法如下:
boolean verified = AlipaySignature.rsaCertCheckV1(
params,
alipayPublicCertPath,
"UTF-8",
params.get("sign_type")
);
具体的方法名称和参数形式,应以项目实际使用的 alipay-sdk-java 版本为准。如果配置中同时包含应用公钥证书、支付宝公钥证书和支付宝根证书,项目通常采用的是证书模式。
排查待验签参数是否被修改
验签必须使用支付宝通知的原始参数值。以下操作都可能导致验签失败:
- 将参数转换成 JSON,再从 JSON 还原。
- 对参数执行二次 URL Decode。
- 对参数值调用
trim()。 - 修改空格、换行、斜杠或特殊字符。
- 错误地将
+处理成空格。 - 修改时间、金额等字段的文本格式。
- 先把参数映射成业务对象,再根据业务对象重新生成 Map。
- 使用经过网关、过滤器或安全组件处理的参数验签。
建议在请求入口处立即提取参数并完成验签,验签通过后再转换成业务对象:
Map<String, String> params = extractParameters(request);
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
params.get("sign_type")
);
if (!verified) {
// 记录必要的排查信息,但不要输出私钥、完整敏感数据等内容
throw new SecurityException("Alipay notification signature verification failed");
}
// 验签成功后再读取 out_trade_no、trade_status 等业务字段
检查 charset 和 sign_type
传入的字符集应与通知参数及应用配置一致。常见配置如下:
String charset = "UTF-8";
String signType = "RSA2";
sign_type 应优先读取通知中的值:
String signType = params.get("sign_type");
如果通知明确使用 RSA2,验签时却传入 RSA,结果会失败。也不要将空值、带有额外空格或大小写错误的 sign_type 直接传给验签方法。
用对比代码定位问题
排查时可以输出 SDK 生成的待验签字符串,但需要在日志中遮蔽买家账号、手机号等敏感信息:
String signContent = AlipaySignature.getSignCheckContentV1(params);
System.out.println("sign_type=" + params.get("sign_type"));
System.out.println("charset=UTF-8");
System.out.println("sign_content=" + signContent);
System.out.println("sign=" + params.get("sign"));
检查时重点关注以下内容:
sign_content是否错误地包含sign。- 参数值是否与收到的通知完全一致。
- 是否出现了
String[]的文本形式,例如[Ljava.lang.String;@...。 - 中文、空格、
+、%、换行等字符是否发生变化。 - 程序实际加载的支付宝公钥是否属于当前应用和当前环境。
不要在日志中打印完整密钥。可以记录公钥的安全摘要,用来确认运行时加载的是哪一份配置。
验签成功后仍需做业务校验
数字签名验证通过,只能说明通知来源和内容完整性没有问题,不能替代业务校验。至少还要核对以下字段和处理逻辑:
app_id是否属于当前商户应用。out_trade_no是否存在,并且属于当前订单。total_amount是否与本地订单金额一致。seller_id是否为预期收款方。trade_status是否属于需要处理的状态。- 同一通知重复到达时,业务处理是否具备幂等性。
业务处理成功后,应按照支付宝异步通知协议返回指定的成功响应。验签失败时不要更新订单状态,也不要仅根据 trade_status 判断支付是否成功。
备注:内容仅供参考。
