PAYATHON 2026

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 仍然指支付宝公钥。

如果项目采用支付宝公钥证书模式,应按照证书模式初始化客户端并完成验签,不能把证书内容当作普通支付宝公钥传入上述方法。

排查顺序

  1. 确认使用的是沙箱网关。
  2. 确认 APPID 属于同一个沙箱应用。
  3. 确认签名使用的是该应用对应的应用私钥。
  4. 确认开放平台上传的应用公钥与该私钥配对。
  5. 确认客户端用于验签的是支付宝公钥,不是应用公钥。
  6. 确认签名算法统一为 RSA2,字符集统一为 UTF-8。
  7. 确认没有混用普通公钥模式和证书模式。
  8. 输出 sub_code、sub_msg 和完整响应体,据此判断 40004 的具体业务原因。
  9. 确认查询使用的 out_trade_no 确实由当前沙箱应用创建。

这些配置一致后,交易查询请求通常不需要手工计算签名。由官方 SDK 生成签名,可以避免参数排序、URL 编码和字符集处理不一致引起的验签错误。

备注:内容仅供参考。