调用App Store Server API获取订阅返回401(无效JWT)求助
Apple StoreKit API调用401(无效JWT)问题排查与修复
你遇到的401错误主要由以下几个可能的问题导致,按优先级排查:
1. JWT末尾的换行符破坏格式
你的代码最后添加了$jwt = $jwt . PHP_EOL;,这会导致生成的JWT末尾带有换行符,Apple服务器解析Bearer Token时会判定为无效。必须删除这行代码,JWT是严格的三段式字符串,不能包含任何额外字符。
2. 缺少私钥加载与签名的错误检查
当前代码没有验证私钥是否成功加载、签名过程是否完成,若私钥文件损坏、权限不足或格式错误,会生成无效签名但无法及时发现。添加以下检查:
// 加载私钥后立即检查 $privateKeyResource = openssl_pkey_get_private($privateKey); if (!$privateKeyResource) { die('加载私钥失败: ' . openssl_error_string()); } // 签名后检查结果 $signature = ''; if (!openssl_sign($unsignedToken, $signature, $privateKeyResource, OPENSSL_ALGO_SHA256)) { die('签名失败: ' . openssl_error_string()); }
3. 验证核心参数的正确性
- Issuer ID:确认是App Store Connect「用户与访问」→「密钥」页面的全局Issuer ID,而非单个密钥的ID。
- Key ID:对应你下载的订阅密钥的Key ID,需与密钥文件名中的ID完全一致。
- Bundle ID(bid字段):必须与App Store Connect中应用的Bundle ID完全匹配,大小写敏感。
- 私钥文件:确保
SubscriptionKey_keyid.p8是从App Store Connect下载的完整文件,包含-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----头尾,且PHP进程有读取该文件的权限。
4. 服务器时间同步问题
JWT的iat(签发时间)和exp(过期时间)依赖服务器本地时间,若你的服务器时间与Apple服务器时间偏差超过5分钟,会被判定为无效token。确保服务器开启NTP时间同步。
修改后的完整JWT生成代码
$keyId = 'keyid'; $issuerId = 'myissuerid'; $privateKey = file_get_contents('SubscriptionKey_keyid.p8'); // JWT Header $header = [ 'alg' => 'ES256', 'kid' => $keyId ]; // JWT Payload $issuedAt = time(); $expirationTime = $issuedAt + (20 * 60); // 有效期不超过60分钟即可 $audience = 'appstoreconnect-v1'; $payload = [ 'iss' => $issuerId, 'iat' => $issuedAt, 'exp' => $expirationTime, 'aud' => $audience, "bid" => "com.targetleaderboard" ]; // Base64URL编码函数 function base64UrlEncode($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } // 编码Header和Payload $base64UrlHeader = base64UrlEncode(json_encode($header)); $base64UrlPayload = base64UrlEncode(json_encode($payload)); $unsignedToken = $base64UrlHeader . "." . $base64UrlPayload; // 加载私钥并检查 $privateKeyResource = openssl_pkey_get_private($privateKey); if (!$privateKeyResource) { die('加载私钥失败: ' . openssl_error_string()); } // 签名并检查 $signature = ''; if (!openssl_sign($unsignedToken, $signature, $privateKeyResource, OPENSSL_ALGO_SHA256)) { die('签名失败: ' . openssl_error_string()); } $base64UrlSignature = base64UrlEncode($signature); // 生成JWT(无多余换行) $jwt = $unsignedToken . "." . $base64UrlSignature;
内容的提问来源于stack exchange,提问作者Ayan
相关产品推荐
相关产品推荐

