支付宝证书异步通知验签时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
如果验签失败或业务处理没有成功,不要返回虚假的成功结果。异步通知入口还应记录必要的验签失败信息,但不能在日志中输出私钥、完整证书内容或其他敏感数据。
备注:内容仅供参考。
