Node.js 使用 alipay-sdk 4.14 报 invalid-signature
结论
这类 invalid-signature 报错不一定说明密钥有误。从支付宝网关生成的待验签字符串来看,实际参与验签的参数只有:
app_id
charset
method
sign_type
timestamp
version
其中不包含 biz_content、return_url 等原本可能参与签名的参数。因此,应优先检查 SDK 生成签名时使用的参数,是否与最终发送到支付宝网关的参数一致。
常见原因有:
bizContent、returnUrl等参数放错了位置,最终没有发出。- SDK 生成 URL 后,又对其进行了手动拼接、解析或重新编码。
sign中的+被转换成空格,或者经过了重复的 URL 编码。- 沙盒应用的私钥与沙盒后台配置的应用公钥不属于同一密钥对。
- 普通公钥模式和证书模式混用。
RSA2、字符集或私钥格式配置不一致。- 使用了错误的沙盒网关,或者混用了沙盒与正式环境的配置。
报错页面中的 & 通常只是 HTML 对 & 的转义显示,不代表实际签名字符串中包含 &。
首先检查最终请求
不要只检查传入 SDK 的 JavaScript 对象,还要查看浏览器或服务端最终请求支付宝时使用的完整 URL。
确认以下参数是否存在:
app_id
biz_content
charset
method
return_url
sign
sign_type
timestamp
version
如果最终 URL 中没有 biz_content,而 SDK 签名时包含了该参数,支付宝就无法计算出相同的签名结果。
还要检查:
sign=abc+def...
是否在中间处理过程中变成了:
sign=abc def...
Base64 签名中的 +、/、= 必须经过正确的 URL 编码。SDK 已经生成 URL 后,不要再次调用 decodeURIComponent() 或 encodeURIComponent(),也不要把 URL 拆成参数再重新拼装。
使用 SDK 直接生成跳转地址
alipay-sdk 4.x 通常可以通过 AlipayFormData 生成页面支付地址。具体导入方式可能会因 CommonJS、ES Module 和安装包的导出形式有所不同,但参数的组织方式应保持一致:
const AlipaySdk = require('alipay-sdk').default;
const AlipayFormData = require('alipay-sdk/lib/form').default;
const alipaySdk = new AlipaySdk({
appId: '你的沙盒APPID',
privateKey: process.env.ALIPAY_APP_PRIVATE_KEY,
alipayPublicKey: process.env.ALIPAY_PUBLIC_KEY,
signType: 'RSA2',
gateway: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
});
async function createPayUrl() {
const formData = new AlipayFormData();
formData.setMethod('get');
formData.addField(
'returnUrl',
'https://example.com/alipay/return'
);
formData.addField('bizContent', {
outTradeNo: `TEST_${Date.now()}`,
productCode: 'FAST_INSTANT_TRADE_PAY',
totalAmount: '0.01',
subject: '沙盒支付测试'
});
return alipaySdk.exec(
'alipay.trade.page.pay',
{},
{ formData }
);
}
createPayUrl()
.then((payUrl) => {
console.log(payUrl);
})
.catch(console.error);
将 SDK 返回的 payUrl 直接用于 HTTP 重定向:
app.get('/pay', async (req, res, next) => {
try {
const payUrl = await createPayUrl();
res.redirect(payUrl);
} catch (error) {
next(error);
}
});
不要在 payUrl 后继续追加业务参数,也不要使用查询字符串库再次序列化。
核对密钥时要区分三种内容
支付宝接入中的几个名称很容易混淆:
- 应用私钥:保存在服务端,用于对请求签名。
- 应用公钥:上传到支付宝开放平台,由支付宝用来验证请求。
- 支付宝公钥:配置在 SDK 中,用于验证支付宝返回的数据或通知。
出现 invalid-signature 时,需要重点核对以下关系:
服务端使用的应用私钥
↕ 必须属于同一密钥对
支付宝开放平台中配置的应用公钥
SDK 配置项 alipayPublicKey 一般填写支付宝公钥,而不是应用公钥。将应用公钥填入该字段,虽然不一定影响请求签名的生成,却会导致后续响应或异步通知验签失败。
可以使用 OpenSSL 从私钥导出应用公钥,再检查它是否与平台配置一致:
openssl pkey -in app_private_key.pem -pubout -out derived_public_key.pem
将导出的公钥正文与沙盒应用后台配置的应用公钥进行比较。比较时可以忽略 PEM 头尾和换行,但正文中的字符必须完全一致。
检查私钥格式
私钥通常以以下两种格式之一保存:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
或者:
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
前者通常是 PKCS#8,后者通常是 PKCS#1。应根据当前 SDK 版本支持的格式进行配置,不能只修改 PEM 的头尾来”转换”格式。
如果私钥保存在环境变量中,换行可能会变成字面量 \n。此时需要将其恢复为真实换行:
const privateKey = process.env.ALIPAY_APP_PRIVATE_KEY.replace(/\\n/g, '\n');
配置 SDK 时使用处理后的值:
const alipaySdk = new AlipaySdk({
appId: process.env.ALIPAY_APP_ID,
privateKey,
alipayPublicKey: process.env.ALIPAY_PUBLIC_KEY.replace(/\\n/g, '\n'),
signType: 'RSA2',
gateway: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
});
不要在日志中输出完整私钥或完整签名。
检查是否混用了证书模式
如果沙盒应用配置的是”应用公钥”,代码也应使用普通公钥模式。
如果应用配置的是证书模式,SDK 通常还需要配置应用公钥证书、支付宝公钥证书和支付宝根证书等内容。不能在平台中使用证书模式,却只向 SDK 传入普通公钥字段。
排查时,先确认开放平台应用使用的加签方式,再检查代码是否采用了相同模式。
检查时间和运行环境
示例报错中的时间是:
2026-01-07 09:43:42
如果这不是脱敏后的示例,而是服务端实际生成的时间,需要检查服务器的时区和系统时间。时间异常通常不会直接改变签名算法的计算结果,但可能触发请求有效期校验,也会干扰问题排查。
同时确认以下沙盒配置来自同一套环境:
沙盒 APPID
沙盒应用对应的应用私钥
沙盒后台配置的应用公钥
沙盒支付宝公钥
沙盒网关
不要将任何正式环境配置混入沙盒配置。
推荐的排查顺序
- 打印 SDK 最终生成的请求 URL,并对
sign做脱敏处理。 - 确认最终 URL 中包含
biz_content和sign。 - 确认 URL 生成后没有再次经过拼接、解码或序列化。
- 检查
sign中的+是否被转换成空格。 - 确认请求使用的是沙盒网关。
- 从当前私钥导出公钥,并与沙盒后台配置的应用公钥比较。
- 确认
signType为RSA2,且没有混用证书模式。 - 检查私钥的 PEM 格式、环境变量换行和文件编码。
- 校准服务器时间后重新生成请求,不要复用旧 URL。
结合当前错误信息,首先要确认的不是密钥是否需要再次更换,而是 SDK 签名时使用的参数集合,是否与支付宝网关最终收到的参数集合完全一致。
备注:内容仅供参考。
