使用Apple API验证交易时如何规避401错误?
我之前在Node.js后端对接StoreKit 2的交易验证时,也踩过一模一样的401认证失败的坑,折腾了好半天才把问题都排查干净,给你分享几个亲测有效的排查和解决方向:
先把P8密钥的“基础检查”做扎实
别小看这个,我当初就是因为下载P8的时候不小心多复制了一个空行,导致密钥格式错误。你要确认:- P8文件是从Apple开发者后台直接下载的,没有手动修改过内容,开头的
-----BEGIN PRIVATE KEY-----和结尾的-----END PRIVATE KEY-----必须完整,中间的密钥部分每行是64个字符,不能有多余的空格、换行或者乱码。 - 你的
loadKeyFromP8File读取后,打印出来的密钥内容要和原文件完全一致,避免读取时的编码问题(虽然你用了utf8,但最好核对一遍)。
- P8文件是从Apple开发者后台直接下载的,没有手动修改过内容,开头的
JWT的参数必须完全贴合Apple的“死要求”
Apple对JWT的字段校验特别严格,差一点都不行:iss:必须是你开发者账号的Team ID(10字符的那个,在会员中心能找到),别把Bundle ID或者其他ID混进来。iat:签发时间要用服务器的Unix时间戳,别用客户端传的时间,服务器时间最好同步下NTP,避免时间差导致校验失败。exp:过期时间最多只能是签发后180天,不能设得更长,比如iat + 86400*180就刚好。aud:沙箱环境填appstoreconnect-v1-sandbox,生产环境填appstoreconnect-v1,我之前就是沙箱测试用了生产的aud,直接401。kid:是你P8密钥对应的密钥ID,创建密钥的时候Apple会显示这个ID,必须和当前P8文件一一对应,不能用其他密钥的kid。
签名算法绝对不能错
Apple只认ES256(ECDSA with P-256 and SHA-256)算法,用错直接401。比如用jose库的话,签名时要明确指定算法;如果用jsonwebtoken,记得要安装elliptic依赖,然后在签名选项里写algorithm: 'ES256',别用默认的HS256。
给你贴一段我现在在用的简化版代码参考:const { SignJWT } = require('jose'); const fs = require('fs'); const { importPKCS8 } = require('jose/webcrypto/pkcs8'); async function generateAppleJWT() { const privateKey = fs.readFileSync('./SomeKey.p8', 'utf8'); const teamId = '你的Team ID'; const keyId = '你的密钥ID'; const isSandbox = true; // 测试时设为true,生产改false const aud = isSandbox ? 'appstoreconnect-v1-sandbox' : 'appstoreconnect-v1'; return await new SignJWT({}) .setProtectedHeader({ alg: 'ES256', kid: keyId }) .setIssuer(teamId) .setIssuedAt() .setExpirationTime('180d') .setAudience(aud) .sign(await importPKCS8(privateKey, 'ES256')); }API端点和环境要配对
沙箱测试一定要用沙箱的API地址:https://api.storekit.itunes.apple.com/inApps/v1/transactions/{transactionId},生产环境用https://buy.itunes.apple.com/inApps/v1/transactions/{transactionId},把沙箱的JWT用到生产端点,或者反过来,肯定会401。最后检查P8密钥的权限
创建P8密钥的时候,必须勾选In-App Purchase的权限!我之前创建密钥的时候漏了这个选项,导致密钥没有访问交易验证API的权限,哪怕JWT生成对了,Apple还是直接返回401。去开发者后台的密钥页面,找到对应的P8密钥,确认它的权限里包含In-App Purchase。
备注:内容来源于stack exchange,提问作者Jedi Exile




