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

PHP中验证Apple内购通知JWS的问题求助

解决Apple内购通知JWS解码验证错误的方案

错误原因

你混淆了Apple共享密钥的用途:验证Apple发送的内购通知JWS需要用Apple的公钥,而非共享密钥。共享密钥是用来生成你请求Apple App Store API时的JWT令牌的,不能用来验证Apple发来的签名。你直接把共享密钥传入Key对象,ES256是ECDSA非对称加密算法,OpenSSL无法将对称密钥解析为合法公钥,所以抛出了格式错误。

正确验证步骤及代码

验证Apple内购通知的JWS,需要先从Apple官方获取对应公钥,再匹配JWS头部的kid字段找到正确密钥,最后完成签名验证和解码:

require_once dirname(__FILE__) . '/vendor/autoload.php';

use Firebase\JWT\JWT;
use Firebase\JWT\JWK;

// 获取Apple内购公钥集合(建议缓存,不要每次请求都拉取)
$jwksContent = file_get_contents('https://api.storekit.itunes.apple.com/inApps/v1/publicKeys');
$jwks = json_decode($jwksContent, true);

// 读取Apple发送的通知内容
$rawNotification = file_get_contents('php://input');
$notificationData = json_decode($rawNotification, true);
$signedPayload = $notificationData["signedPayload"];

// 解析JWS头部,提取kid字段用于匹配公钥
$jwsParts = explode('.', $signedPayload);
$headerRaw = str_replace(['-', '_'], ['+', '/'], $jwsParts[0]);
$header = json_decode(base64_decode($headerRaw), true);
$targetKid = $header['kid'];

// 从公钥集合中找到匹配的JWK
$matchedJwk = null;
foreach ($jwks['keys'] as $key) {
    if ($key['kid'] === $targetKid) {
        $matchedJwk = $key;
        break;
    }
}

if (!$matchedJwk) {
    die('无法找到匹配的Apple内购公钥');
}

// 将JWK转换为可用于验证的密钥对象
$verificationKey = JWK::parseKey($matchedJwk);

// 解码并验证JWS签名
try {
    $decodedPayload = JWT::decode($signedPayload, $verificationKey);
    // 处理解码后的内购数据
    var_dump($decodedPayload);
} catch (Exception $e) {
    echo '签名验证失败: ' . $e->getMessage();
}

关键注意事项

  • 缓存公钥:不要每次处理通知都请求Apple的公钥接口,建议将公钥缓存1小时以上,减少请求开销
  • 共享密钥的正确用法:当你需要主动调用Apple App Store API(比如查询订单、退款)时,才用共享密钥生成JWT令牌,作为请求的Authorization头
  • 环境要求:确保PHP环境已安装OpenSSL扩展,且版本支持ES256算法

内容的提问来源于stack exchange,提问作者Camille Gallet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 04:24:18