PAYATHON 2026

支付宝支付结果通知调用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 的签名规则,否则不要自行删除、拼接或排序参数。

核对支付宝公钥

即使已经确认使用的是支付宝公钥,也要继续检查该公钥是否与当前通知匹配:

  1. 生产环境的通知必须使用生产环境对应的支付宝公钥。
  2. 沙箱通知必须使用沙箱环境对应的支付宝公钥。
  3. 不同应用的密钥配置不能混用。
  4. 重新生成密钥或修改配置后,需要确认程序实际加载的是当前有效配置。
  5. 不要把应用公钥、应用公钥证书或支付宝公钥证书误当成普通支付宝公钥。

使用普通公钥模式时,传入 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 判断支付是否成功。

备注:内容仅供参考。