PAYATHON 2026

支付宝 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 从应用公钥证书地址改为当前环境中有效的支付宝公钥证书地址。

备注:内容仅供参考。