PAYATHON 2026

支付宝网站支付同步回调使用 rsaCheckV1 验签失败

支付小周

结论

sign 和 sign_type 应从支付宝跳转到 return_url 时携带的原始 HTTP 请求参数中获取,不能从支付接口返回的 AlipayTradePagePayResponse、业务对象或手工筛选后的参数集合中获取。

你提供的参数集合中没有 sign,因此无法通过 AlipaySignature.rsaCheckV1 验签。签名值也不能根据其他参数重新计算或补全。需要检查参数获取、框架绑定、重定向和网关转发过程。如果最终收到的请求中确实没有 sign,就必须按验签失败处理。

为什么参数中没有 sign

支付宝正常访问 return_url 时,会同时携带业务参数和以下两个参数:

sign
sign_type

签名参数缺失,常见原因有:

  1. 获取的不是 return_url 对应的原始请求参数,而是支付接口响应对象中的字段。
  2. Controller 通过 DTO、实体类或固定参数列表接收回调,但对象中没有定义 sign 和 sign_type。
  3. 构造验签参数 Map 时,只复制了部分业务字段。
  4. 前端页面、反向代理或中间跳转地址重新拼接 URL 时遗漏了参数。
  5. 输出日志前,代码已经删除了 sign 和 sign_type。
  6. 代码读取了请求体,而同步跳转参数实际位于 URL 查询字符串中。

可以先查看浏览器地址栏、开发者工具,或服务端记录的原始 queryString,确认支付宝发起的跳转请求是否包含签名参数。

正确获取同步回调参数

在 Java Servlet 或 Spring MVC 中,应通过 HttpServletRequest.getParameterMap() 获取完整参数:

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

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

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

        StringBuilder value = new StringBuilder();
        for (int i = 0; i < values.length; i++) {
            if (i > 0) {
                value.append(",");
            }
            value.append(values[i]);
        }

        params.put(entry.getKey(), value.toString());
    }

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

    if (sign == null || sign.isEmpty()) {
        return false;
    }

    if (signType == null || signType.isEmpty()) {
        return false;
    }

    return AlipaySignature.rsaCheckV1(
        params,
        ALIPAY_PUBLIC_KEY,
        "UTF-8",
        signType
    );
}

这里的:

ALIPAY_PUBLIC_KEY

必须是支付宝公钥,不能使用应用公钥或应用私钥。使用支付宝开放平台公钥模式时,应填写当前应用对应的支付宝公钥。使用证书模式时,则要采用证书模式对应的 SDK 验签方法和配置,不能直接沿用普通公钥模式的代码。

Spring MVC 的处理方法可以这样写:

@GetMapping("/alipay/return")
public String alipayReturn(HttpServletRequest request) {
    try {
        Map<String, String> params = new HashMap<>();

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

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

        if (sign == null || sign.isEmpty()
                || signType == null || signType.isEmpty()) {
            return "pay/verify-failed";
        }

        boolean verified = AlipaySignature.rsaCheckV1(
            params,
            ALIPAY_PUBLIC_KEY,
            "UTF-8",
            signType
        );

        if (!verified) {
            return "pay/verify-failed";
        }

        // 验签通过后,还应核对 app_id、out_trade_no、total_amount、
        // seller_id 等参数是否与本地订单一致。
        return "pay/success";
    } catch (Exception e) {
        return "pay/verify-failed";
    }
}

不建议只在 Controller 中声明部分参数,例如:

@GetMapping("/alipay/return")
public String alipayReturn(
        String out_trade_no,
        String trade_no,
        String total_amount) {
    // 这里没有取得完整回调参数,无法正确验签。
    return "pay/success";
}

也不要先把请求转换为只含业务字段的对象,再用该对象生成验签 Map。

没有 sign 时不能验签

rsaCheckV1 会使用支付宝公钥验证支付宝提供的数字签名。完成验证至少需要:

  • 支付宝返回的完整待验签参数;
  • 支付宝返回的 sign;
  • 正确的支付宝公钥;
  • 与回调一致的字符集;
  • 正确的签名类型,通常为 RSA2。

没有 sign,就没有可供验证的数字签名。因此,下面这种做法没有意义:

params.put("sign", "自行生成的签名");

应用私钥生成的是应用方签名,无法代替支付宝为同步回调参数生成的签名。订单号、交易号或金额也不足以证明这次跳转可信。

排查方法

排查时,可以临时记录参数名称和原始查询字符串:

String queryString = request.getQueryString();

request.getParameterMap().forEach((name, values) -> {
    System.out.println(name + "=" + String.join(",", values));
});

生产环境中不要长期打印完整签名、用户信息或其他敏感参数。

如果 queryString 中有 sign,但业务 Map 中没有,问题通常出在参数转换、DTO 绑定或字段过滤环节。如果 queryString 本身就没有 sign,需要继续检查:

  • 当前请求是否确实由支付宝直接跳转而来;
  • return_url 是否又经过了一层前端或服务端重定向;
  • 中间页面拼接 URL 时是否只保留了部分参数;
  • 网关或代理是否设置了查询参数白名单;
  • 测试时是否手工复制了不完整的回调 URL。

注意事项

同步回调依赖用户浏览器完成跳转。用户可能关闭页面、遇到断网,或者阻止跳转,所以不能把同步回调作为更新订单支付状态的唯一依据。

订单最终状态应以支付宝异步通知 notify_url 的验签结果为主,必要时再主动调用交易查询接口确认。同步回调主要用于展示支付结果页面。

即使同步回调已经通过验签,仍要核对以下字段:

app_id
seller_id
out_trade_no
total_amount

这些字段必须与当前应用、收款账号和本地订单一致。不能因为页面收到了 trade_no,就直接把订单标记为支付成功。

备注:内容仅供参考。