PAYATHON 2026

支付宝证书异步通知验签时certVerify的content传什么?

支付老李

明确结论

AlipaySignature.certVerify 的第一个参数 content,应传入根据支付宝异步通知参数生成的待验签原文。

生成这段原文时,需要:

  • 保留支付宝回调中的所有有效业务参数;
  • 排除 sign 和 sign_type;
  • 按参数名排序;
  • 按支付宝 SDK 的规则拼接成 key=value&key=value;
  • 保留通知参数的原始值,不要提前进行 JSON 格式化、URL 编码或业务字段转换。

不能只传 biz_content、trade_status 或订单号,也不能直接传入随意序列化后的参数 Map。

为什么不能直接传请求体

支付宝异步通知通常通过表单参数发送。签名对应的是通知参数按规定拼接后得到的字符串,不是请求体或某个单独字段。

例如,回调参数经过过滤和排序后,待验签内容可能是:

app_id=2021000000000000&charset=utf-8&gmt_payment=2026-09-09 12:00:00&out_trade_no=ORDER_10001&trade_no=2026090922000000000001&trade_status=TRADE_SUCCESS

具体参与签名的字段以支付宝实际发送的通知为准。手工拼接容易漏掉字段、打乱顺序或改变参数值,建议直接使用 SDK 提供的方法验签。

推荐做法:直接使用证书模式验签方法

在 Java SDK 中,证书模式通常可以直接调用 rsaCertCheckV1,并将完整的异步通知参数 Map 交给 SDK。SDK 会排除签名字段,并生成待验签原文。

import com.alipay.api.internal.util.AlipaySignature;

import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public boolean verifyNotify(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);
    }

    return AlipaySignature.rsaCertCheckV1(
        params,
        "/path/to/alipayCertPublicKey_RSA2.crt",
        "UTF-8",
        "RSA2"
    );
}

这里需要使用支付宝公钥证书,文件名通常类似:

alipayCertPublicKey_RSA2.crt

不要误用以下文件:

  • 应用私钥;
  • 应用公钥证书;
  • 支付宝根证书。

不同版本的支付宝 Java SDK 可能采用不同的方法签名,请以项目实际引入版本中的 AlipaySignature API 为准。

必须调用 certVerify 时怎么传

如果当前 SDK 或现有代码明确要求调用 certVerify,先根据通知参数生成待验签字符串,再将它作为 content 传入。

Map<String, String> params = new HashMap<>();

request.getParameterMap().forEach((name, values) -> {
    params.put(name, String.join(",", values));
});

String sign = params.get("sign");

// SDK 方法会排除 sign、sign_type,并按规则生成待验签字符串
String content = AlipaySignature.getSignCheckContentV1(params);

// certVerify 的其他参数应按当前 SDK 实际方法签名传入
boolean verified = AlipaySignature.certVerify(
    content,
    sign,
    alipayPublicKey,
    "UTF-8"
);

部分 SDK 版本对 certVerify 的参数或证书处理方式有不同定义。如果第三个参数需要公钥字符串,就要从支付宝公钥证书中取得公钥;如果该参数需要证书路径,则按照当前版本传入对应路径。不能只看方法名来判断参数含义。

常见错误

只传某个业务字段

以下内容都不能单独作为 content:

params.get("biz_content");
params.get("trade_status");
params.get("out_trade_no");
requestBody;

验签所需的是根据完整通知参数集合生成的签名原文。

对参数值再次编码或解码

取得参数后,不要再自行执行:

URLDecoder.decode(value, "UTF-8");
URLEncoder.encode(value, "UTF-8");

重复编解码可能改变空格、加号、百分号或中文字符,导致验签失败。

使用 JSON 重新序列化参数

不要将参数 Map 转成 JSON 后再传入:

String content = objectMapper.writeValueAsString(params);

JSON 的字段顺序、引号和数据结构都与支付宝的签名原文不同。

验签成功就直接处理业务

验签通过后,还需要检查相关业务信息,包括:

  • app_id 是否属于当前应用;
  • out_trade_no 是否存在;
  • 金额和币种是否与本地订单一致;
  • 收款方信息是否符合预期;
  • 通知状态是否允许更新业务;
  • 同一通知是否已经处理。

支付宝可能重复发送异步通知,因此业务处理必须具备幂等性。

处理结果

只有在验签和业务处理全部成功后,才响应以下纯文本:

success

如果验签失败或业务处理没有成功,不要返回虚假的成功结果。异步通知入口还应记录必要的验签失败信息,但不能在日志中输出私钥、完整证书内容或其他敏感数据。

备注:内容仅供参考。