SpringBoot 支付宝网站支付报 invalid-signature 验签错误
结论
invalid-signature 通常不是 AlipayTradeQueryRequest 的业务参数引起的,而是签名过程中使用的密钥、环境或参数不一致。
最容易混淆的是三种密钥的用途:
- 应用向支付宝发送请求时,用“应用私钥”签名。
- 应用验证支付宝返回的数据时,用“支付宝公钥”验签。
- “应用公钥”需要上传到支付宝开放平台,不能用于验证支付宝的响应。
40004 是“业务处理失败”的通用错误码。要判断具体原因,需要查看响应中的 sub_code 和 sub_msg,不能只根据 code 和 msg 认定是验签失败。
先确认密钥有没有用反
创建 AlipayClient 时,各参数应这样对应:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
appPrivateKey, // 应用私钥
"json",
"UTF-8",
alipayPublicKey, // 支付宝公钥,不是应用公钥
"RSA2"
);
三种密钥分别有以下用途:
| 配置项 | 用途 |
|---|---|
| 应用私钥 | 应用生成请求签名,保存在服务端 |
| 应用公钥 | 上传到支付宝开放平台,供支付宝验证应用签名 |
| 支付宝公钥 | 应用验证支付宝响应和通知的签名 |
如果把 alipayPublicKey 配置成应用公钥,即使密钥字符串本身没有问题,验签仍会失败。
确认沙箱配置来自同一套环境
使用沙箱应用时,下面这些配置都必须来自对应的沙箱环境:
alipay.gateway-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
alipay.app-id=沙箱应用APPID
alipay.app-private-key=沙箱应用对应的应用私钥
alipay.alipay-public-key=沙箱应用页面显示的支付宝公钥
alipay.sign-type=RSA2
alipay.charset=UTF-8
以下配置不能混用:
- 正式环境
APPID和沙箱网关; - 沙箱
APPID和正式环境支付宝公钥; - 新生成的应用私钥和旧应用公钥;
- 普通公钥模式和支付宝公钥证书模式;
RSA和RSA2。
重新生成应用密钥后,还要把新的应用公钥上传到开放平台。如果只修改本地私钥,没有同步更新平台上的应用公钥,支付宝就无法验证请求签名。
正确调用交易查询接口
参数拼装和签名最好交给支付宝 Java SDK 处理,不要自行拼接待签名字符串。
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
public class AlipayQueryService {
private final AlipayClient alipayClient;
public AlipayQueryService(
String gatewayUrl,
String appId,
String appPrivateKey,
String alipayPublicKey) {
this.alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
}
public AlipayTradeQueryResponse query(String outTradeNo)
throws AlipayApiException {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
System.out.println("code: " + response.getCode());
System.out.println("msg: " + response.getMsg());
System.out.println("subCode: " + response.getSubCode());
System.out.println("subMsg: " + response.getSubMsg());
System.out.println("body: " + response.getBody());
return response;
}
}
查询时,out_trade_no 必须是商户创建支付订单时生成的订单号。也可以按照接口要求使用支付宝交易号 trade_no,但不能传入网页支付过程中展示的其他流水号。
查看 40004 的详细信息
不要只记录以下内容:
code=40004
msg=Business Failed
还要记录:
response.getSubCode();
response.getSubMsg();
response.getBody();
根据返回内容,问题通常分为两类:
- 如果
sub_code指向签名、权限或应用配置,应继续检查密钥和环境。 - 如果
sub_code表示交易不存在、订单状态异常或参数错误,说明请求已经通过验签,问题出在业务参数上。
所以,40004 不等于 invalid-signature。它们可能来自不同的请求,也可能是签名问题修复后出现的下一层业务错误。
Spring Boot 配置中的常见问题
私钥被换行或空格破坏
如果密钥保存在 application.yml、环境变量或配置中心,需要检查程序实际读到的内容,确认其中没有多余的空格、引号或换行。
建议保存为不带头尾标识的单行 Base64 字符串:
MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
除非当前 SDK 或密钥加载代码明确支持 PEM,否则不要把下面的头尾标识也填入只接受密钥正文的配置项:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
可以只检查密钥长度,不要打印密钥正文:
System.out.println("privateKey length: " + appPrivateKey.length());
System.out.println("alipayPublicKey length: " + alipayPublicKey.length());
生产日志中不要输出完整私钥。
私钥格式不符合要求
支付宝 Java SDK 的常见配置使用 PKCS#8 格式的应用私钥。以下头标识通常表示 PKCS#8:
-----BEGIN PRIVATE KEY-----
以下形式通常是 PKCS#1:
-----BEGIN RSA PRIVATE KEY-----
实际支持哪种格式,取决于 SDK 版本和密钥加载方式。如果 SDK 报告密钥解析异常,应先检查私钥格式,不要反复更换公钥。
配置值中含有不可见字符
从网页或文本编辑器复制密钥时,可能会带入以下内容:
- UTF-8 BOM;
- 全角空格;
- 回车符
\r; - 首尾空格;
- 被转义的字符串
\n。
可以先清理首尾空白:
String privateKey = configuredPrivateKey.trim();
String alipayPublicKey = configuredAlipayPublicKey.trim();
如果配置中保存的是带换行的 PEM,应使用可靠的密钥加载方式,不能简单删除所有字符并假定格式仍然正确。
回调验签也使用支付宝公钥
网页支付的同步跳转参数和异步通知参数需要分别验签。验签前要保留支付宝传来的原始参数值,不要先进行 JSON 序列化、类型转换或重新编码。
import com.alipay.api.internal.util.AlipaySignature;
boolean verified = AlipaySignature.rsaCheckV1(
request.getParameterMap(),
alipayPublicKey,
"UTF-8",
"RSA2"
);
这里的 alipayPublicKey 仍然指支付宝公钥。
如果项目采用支付宝公钥证书模式,应按照证书模式初始化客户端并完成验签,不能把证书内容当作普通支付宝公钥传入上述方法。
排查顺序
- 确认使用的是沙箱网关。
- 确认
APPID属于同一个沙箱应用。 - 确认签名使用的是该应用对应的应用私钥。
- 确认开放平台上传的应用公钥与该私钥配对。
- 确认客户端用于验签的是支付宝公钥,不是应用公钥。
- 确认签名算法统一为
RSA2,字符集统一为UTF-8。 - 确认没有混用普通公钥模式和证书模式。
- 输出
sub_code、sub_msg和完整响应体,据此判断40004的具体业务原因。 - 确认查询使用的
out_trade_no确实由当前沙箱应用创建。
这些配置一致后,交易查询请求通常不需要手工计算签名。由官方 SDK 生成签名,可以避免参数排序、URL 编码和字符集处理不一致引起的验签错误。
备注:内容仅供参考。
