You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:27:11