feat: 部署时自动校验支付宝签名一致性 + 验签失败升级为 error + 密钥核对脚本

This commit is contained in:
2026-08-10 16:06:02 +08:00
parent 49d740e27c
commit 366d62f262
3 changed files with 147 additions and 2 deletions

View File

@@ -499,6 +499,85 @@ test_notify_url_domain() {
return 0
}
# ---------- 支付宝签名一致性校验 ----------
# 防止密钥不同步导致的 invalid-signature历史事故服务器私钥与控制台应用公钥不匹配
test_payment_signature() {
log_info "校验支付宝支付签名与本地密钥一致性..."
if [ ! -f "${SCRIPT_DIR}/.env" ] || ! grep -q "ALIPAY_PRIVATE_KEY" "${SCRIPT_DIR}/.env"; then
log_warn " 本地 .env 无 ALIPAY_PRIVATE_KEY跳过签名校验"
return 0
fi
local jwt
jwt=$(remote "curl -sk -X POST '${BASE_URL}/api/guest-login'" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
if [ -z "$jwt" ]; then
log_error "获取 JWT 失败,无法校验支付签名"
return 1
fi
local form
form=$(remote "curl -sk --max-time 10 '${BASE_URL}/payment/page?package=monthly&jwt=${jwt}'" 2>/dev/null || true)
if ! echo "$form" | grep -q 'name="sign"'; then
log_warn " 支付表单未生成(可能为 Mock 支付模式),跳过签名校验"
return 0
fi
local result
result=$(echo "$form" | python3 -c "
import base64, re, html, sys
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
priv_b64 = None
for line in open('${SCRIPT_DIR}/.env'):
if line.startswith('ALIPAY_PRIVATE_KEY='):
priv_b64 = line.strip().split('=', 1)[1]
break
if not priv_b64:
print('FAIL: 本地 .env 无 ALIPAY_PRIVATE_KEY')
sys.exit(1)
try:
priv = serialization.load_der_private_key(base64.b64decode(priv_b64), password=None)
except Exception:
try:
priv = serialization.load_pem_private_key(priv_b64.encode(), password=None)
except Exception as e:
print(f'FAIL: 私钥解析失败: {e}')
sys.exit(1)
content = sys.stdin.read()
fields = {}
for m in re.finditer(r'name=\"([^\"]+)\" value=\"([^\"]*)\"', content):
fields[m.group(1)] = html.unescape(m.group(2))
if 'sign' not in fields:
print('FAIL: 表单无 sign 字段')
sys.exit(1)
sign = fields.pop('sign')
sign_source = '&'.join(f'{k}={v}' for k, v in sorted(fields.items()))
try:
our_sign = base64.b64encode(priv.sign(sign_source.encode('utf-8'), padding.PKCS1v15(), hashes.SHA256())).decode()
except Exception as e:
print(f'FAIL: 签名失败: {e}')
sys.exit(1)
if our_sign == sign:
print('PASS')
else:
print('FAIL: 服务器签名与本地私钥不一致(密钥不同步!检查服务器 .env 与控制台应用公钥)')
sys.exit(1)
" 2>/dev/null)
if [ "$result" = "PASS" ]; then
log_info " 服务器签名与本地私钥一致 ✅"
return 0
else
log_error " ${result:-签名校验脚本执行失败}"
return 1
fi
}
# ---------- 主测试入口 ----------
run_tests() {
log_step "执行部署后测试..."
@@ -522,6 +601,7 @@ run_tests() {
"test_payment_refund:退款+会员撤销"
"test_payment_verify_detection:验证函数检测逻辑"
"test_notify_url_domain:支付回调域名校验"
"test_payment_signature:支付签名一致性"
)
local i=1 total=${#tests[@]}