支付宝 App 支付异步通知验签失败如何解决?
结论
验签失败通常是因为 CERT_PATH 指向了错误的证书类型。
AlipaySignature.rsaCertCheckV1(...) 的第二个参数必须是支付宝公钥证书的文件地址,通常对应从开放平台下载的 alipayCertPublicKey_RSA2.crt。这里不能使用应用公钥证书或支付宝根证书。
boolean flag = AlipaySignature.rsaCertCheckV1(
params,
ALIPAY_PUBLIC_CERT_PATH,
"UTF-8",
"RSA2"
);
为什么应用公钥证书不能用于验签
异步通知由支付宝使用支付宝私钥签名。商户收到通知后,需要用与该私钥配对的支付宝公钥验签。
证书模式通常包含三类证书:
| 证书 | 用途 |
|---|---|
| 应用公钥证书 | 包含应用公钥,支付宝用它验证商户请求的签名 |
| 支付宝公钥证书 | 包含支付宝公钥,商户用它验证支付宝返回内容和异步通知的签名 |
| 支付宝根证书 | 用于校验证书链,以及计算或识别相关证书序列号 |
应用公钥与支付宝私钥并不配对,因此用应用公钥证书验证支付宝发送的签名必然会失败。
正确配置证书地址
从支付宝开放平台下载证书后,可以分别配置以下三个文件:
private static final String APP_CERT_PATH =
"/path/to/appCertPublicKey.crt";
private static final String ALIPAY_PUBLIC_CERT_PATH =
"/path/to/alipayCertPublicKey_RSA2.crt";
private static final String ALIPAY_ROOT_CERT_PATH =
"/path/to/alipayRootCert.crt";
验证异步通知时应使用:
ALIPAY_PUBLIC_CERT_PATH
也就是 alipayCertPublicKey_RSA2.crt,不能使用 APP_CERT_PATH。
不同的下载方式或配置环境可能产生不同的文件名。判断证书类型时,应以文件实际内容和开放平台的标注为准,不要只看文件名。
Java 验签示例
收集支付宝 POST 请求中的全部参数,然后直接交给 SDK 验签:
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) 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();
String value = String.join(",", values);
params.put(entry.getKey(), value);
}
String alipayPublicCertPath =
"/path/to/alipayCertPublicKey_RSA2.crt";
return AlipaySignature.rsaCertCheckV1(
params,
alipayPublicCertPath,
"UTF-8",
"RSA2"
);
}
如果项目使用 javax.servlet,将示例中的导入改为:
import javax.servlet.http.HttpServletRequest;
验签通过后,还需要核对通知中的业务数据:
if (verifyAlipayNotify(request)) {
// 继续校验 app_id、seller_id、out_trade_no、total_amount 等业务字段
// 确认无误后更新订单状态
} else {
// 记录验签失败日志,不处理订单
}
仍然验签失败时的排查顺序
1. 确认证书属于当前应用和当前环境
正式环境和沙箱环境的应用配置、密钥、证书不能混用。同时,要确认支付宝公钥证书是在当前应用的证书配置生效后下载的。
如果开放平台重新生成或更新过证书,本地旧证书可能已经无法匹配,需要重新下载并部署。
2. 检查 alipay_cert_sn
证书模式的通知参数通常包含 alipay_cert_sn,用于标识支付宝签名时使用的证书。
支付宝证书更新后,应根据该序列号选取对应的支付宝公钥证书。如果系统只保存了一份旧证书,可能会出现之前验签正常、证书更新后突然失败的情况。
是否需要自行维护多张证书,取决于所用支付宝 Java SDK 的版本和证书更新方案。至少要记录通知中的 alipay_cert_sn,方便与当前部署的支付宝公钥证书核对。
3. 不要修改待验签参数
验签必须使用支付宝通知中的原始参数集合。以下操作都可能造成验签失败:
- 只保留部分业务参数;
- 将参数转换成 JSON 后再验签;
- 对参数排序、拼接或重新编码后再传入;
- 修改空格、加号、换行等字符;
- 对已经解码的参数再次执行 URL 解码;
- 根据日志文本或数据库记录重新构造验签参数。
应优先通过 request.getParameterMap() 获取本次 HTTP 请求的参数。
4. 检查字符集和签名类型
通知参数中的 charset 通常是 utf-8,sign_type 通常是 RSA2。调用验签方法时,要与实际通知保持一致:
String charset = params.getOrDefault("charset", "UTF-8");
String signType = params.getOrDefault("sign_type", "RSA2");
boolean verified = AlipaySignature.rsaCertCheckV1(
params,
ALIPAY_PUBLIC_CERT_PATH,
charset,
signType
);
如果通知使用的是 RSA2,不要传入 RSA。
5. 确认证书文件可以被正确读取
CERT_PATH 必须是运行中的 Java 进程能够访问的真实文件路径。项目部署到 JAR、Docker 或服务器后,本地开发环境中的相对路径往往会失效。
可以在启动时检查证书文件:
import java.nio.file.Files;
import java.nio.file.Path;
Path certPath = Path.of(ALIPAY_PUBLIC_CERT_PATH);
if (!Files.isRegularFile(certPath) || !Files.isReadable(certPath)) {
throw new IllegalStateException(
"支付宝公钥证书不存在或不可读:" + certPath.toAbsolutePath()
);
}
如果证书放在 resources 中并打包进 JAR,它不一定能直接转换成普通文件路径。此时应根据当前 SDK 接口的要求,将证书部署为外部文件,或者改用该 SDK 版本支持的证书内容、输入流等配置方式。
验签通过后仍需做业务校验
签名正确只能说明通知由对应的支付宝密钥签发,不能据此直接更新订单。至少还要检查:
app_id是否为当前应用;seller_id是否为当前商户;out_trade_no是否存在并属于当前订单;total_amount是否与订单金额一致;trade_status是否为需要处理的状态;- 订单是否已经处理,避免重复通知导致重复记账或重复发货。
完成全部验证并成功处理后,响应以下纯文本:
success
如果处理失败,不要返回 success,否则支付宝会认为通知已经处理完成。
核心修正是把 CERT_PATH 从应用公钥证书地址改为当前环境中有效的支付宝公钥证书地址。
备注:内容仅供参考。
