支付宝异步回调服务器IP列表及白名单网段
明确结论
支付宝异步通知的源 IP 通常没有稳定、公开且长期有效的固定列表。源 IP 白名单只能作为辅助措施,不适合充当支付通知的主要安全校验手段。支付宝可能因扩容、容灾或网络调度调整出口 IP,因此不要把网上收集的单个 IP 或网段设为永久白名单。
商户应允许支付宝访问异步通知地址,并通过支付宝签名、商户身份、订单金额等信息核实通知,同时做好幂等处理。如果前置机必须配置 IP 白名单,应向支付宝官方技术支持申请与当前业务对应的网络信息,并确认支付宝是否承诺提供固定网段。没有明确承诺时,不要假设任何 IP 列表会长期有效。
为什么不能只依赖 IP 白名单
源 IP 白名单只能判断请求来自哪个网络地址,不能证明通知内容未经篡改。即使源 IP 符合规则,商户也必须验证通知签名。
异步通知还可能经过负载均衡、代理或网络地址转换。应用服务器最终看到的可能是内部代理 IP,而 X-Forwarded-For 等请求头可以由客户端伪造。只有当商户完全控制的可信代理会重写这些请求头时,才能将其用于判断请求来源,但仍不能用它代替签名验证。
建议按以下方式进行校验:
网络访问控制(辅助)
+
支付宝 RSA/RSA2 签名验证(必须)
+
app_id、seller_id、订单号、金额校验(必须)
+
订单状态机与幂等处理(必须)
推荐处理步骤
1. 为通知接口提供公网 HTTPS 地址
异步通知地址必须能被支付宝服务器访问,不能要求登录,也不要依赖浏览器 Cookie。例如:
https://pay.example.com/alipay/notify
如果防火墙只允许固定源 IP 访问该地址,一旦支付宝更换出口地址,通知就可能无法送达。
2. 使用原始通知参数验签
验签时,应使用支付宝实际提交的全部参数,排除 sign 和 sign_type,并使用支付宝公钥,不能使用商户应用私钥。
以下示例使用支付宝 Java SDK:
@PostMapping(
value = "/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
public String handleAlipayNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((name, values) -> {
params.put(name, String.join(",", values));
});
try {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
params.getOrDefault("sign_type", "RSA2")
);
if (!verified) {
return "failure";
}
String appId = params.get("app_id");
String sellerId = params.get("seller_id");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
if (!expectedAppId.equals(appId)) {
return "failure";
}
if (!expectedSellerId.equals(sellerId)) {
return "failure";
}
Order order = orderService.findByOrderNo(outTradeNo);
if (order == null) {
return "failure";
}
BigDecimal notifiedAmount = new BigDecimal(totalAmount);
if (order.getAmount().compareTo(notifiedAmount) != 0) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIdempotently(
outTradeNo,
tradeNo,
notifiedAmount
);
}
return "success";
} catch (Exception e) {
return "failure";
}
}
具体使用哪个 SDK 方法以及如何传参,应以项目当前采用的支付宝 SDK 版本为准。验签前不要自行对参数做 URL 解码、重新编码或拼接,否则字符处理方式的差异很容易造成验签失败。
3. 校验业务数据
验签通过后,仍不能直接修改订单状态。至少需要检查以下内容:
app_id是否属于当前商户应用;seller_id是否是预期的收款支付宝账号;out_trade_no是否存在并属于当前业务;total_amount是否与订单应付金额完全一致;trade_status是否是业务认可的支付状态;- 已保存的支付宝交易号是否与
trade_no一致。
金额应使用 BigDecimal 或以”分”为单位的整数比较,不能使用 double:
BigDecimal expected = new BigDecimal("99.90");
BigDecimal actual = new BigDecimal(totalAmount);
if (expected.compareTo(actual) != 0) {
throw new IllegalArgumentException("支付金额不一致");
}
4. 做好幂等处理
支付宝可能多次发送同一笔交易的异步通知。更新订单时,应通过事务、唯一约束或条件更新保证幂等。例如:
UPDATE payment_order
SET status = 'PAID',
alipay_trade_no = :tradeNo,
paid_at = CURRENT_TIMESTAMP
WHERE order_no = :orderNo
AND status = 'PENDING';
同一通知即使到达多次,也只能触发一次发货、充值或权益发放。
5. 正确返回处理结果
通知处理成功后,直接返回以下纯文本:
success
不要返回 JSON、HTML 页面或其他说明。验签失败、订单不存在、金额不符或内部处理尚未成功时,不要返回 success。否则支付宝可能认为通知已经正常接收,并停止重试。
必须配置 IP 白名单时怎么办
如果企业安全策略要求前置机只接受指定源地址,可以这样处理:
- 联系支付宝官方技术支持,说明所用产品、环境和接入方式,确认当前业务是否有可用的出口 IP 或网段。
- 确认这些地址是否属于支付宝长期承诺的固定网段,以及地址变更时是否会提供通知。
- 让防火墙或网关配置能够快速更新,不要把地址硬编码在应用程序中。
- 监控因白名单拦截造成的通知失败,并保留主动查询交易状态的补偿机制。
- 配置白名单后,仍要继续验证签名和订单数据。
不要根据 DNS 解析结果生成永久白名单,也不要直接采用非官方文章中的历史 IP。负载均衡地址、DNS 解析结果和出口节点都有可能变化。
注意事项
- 不要把同步跳转页面作为支付成功的依据。浏览器跳转可能被关闭、拦截或伪造。
- 不要只根据
trade_status更新订单。必须先验签,再核对订单金额等业务字段。 - 不要无条件信任
X-Forwarded-For、X-Real-IP等请求头。 - 支付宝公钥和商户应用公钥不是同一个概念。验签时,应使用与当前应用配置相匹配的支付宝公钥。
- 证书模式与普通公钥模式使用不同的密钥加载方式,应按项目实际采用的接入模式处理。
- 异步通知的处理流程应尽量简短。订单落库后,可以通过消息队列执行耗时的发货或权益发放任务。
- 应定期调用支付宝交易查询接口,核对长时间处于”待支付”状态的订单,防止网络或白名单配置异常造成漏单。
备注:内容仅供参考。
