【问题标题】:ES256 JWT Signing in PHP for Apple AppStoreConenct API AuthES256 JWT 在 PHP 中为 Apple AppStoreConenct API 身份验证签名
【发布时间】:2019-10-30 08:11:58
【问题描述】:

我正在尝试使用 PHP 使用 ES256 签名的 JWT(根据他们在 https://developer.apple.com/documentation/appstoreconnectapi 的说明)对 Apple 的 AppStoreConnect API 进行身份验证。

发送我的请求总是会导致 401 NOT_AUTHORIZED 错误。

我已经验证我的标题和声明的内容是正确的 - 我什至在网上找到了一个 Ruby 脚本,用于生成一个 ES256 签名的 JWT 并使用我的 Apple 提供的颁发者、密钥 ID、私钥,它可以正常工作 - Apple接受令牌。这告诉我我的凭据很好,我在 php 中做错了。

除非我只是盯着这段代码太久,否则 JWT 格式正确,base64 编码正确,并且在 header 中正确设置了不记名令牌。

为了排除请求发送的问题,我尝试了 GuzzleHTTP 和 CLI cURL - 都是 401。


这是相关代码。您会看到create 方法正在对标头和声明进行编码,对“有效负载”进行签名,然后将所有 3 个连接起来。

public function create()
{
    $header = $this->encode(
        json_encode([
            'kid' => 'my_key_id',
            'alg' => 'ES256',
            'typ' => 'JWT',
        ])
    );

    $claims = $this->encode(
        json_encode([
            'iss' => 'my_issuer_uuid',
            'exp' => time() + (20 * 60),
            'aud' => 'appstoreconnect-v1',
        ])
    );

    $signature = $this->encode(
        $this->sign("$header.$claims")
    );

    return $header . '.' . $claims . '.' . $signature;
}

此代码成功返回一个打开的 ssl 资源,$data 具有预期的内容。

public function sign($data)
{
    if (!$key = openssl_pkey_get_private('file://my_key_file.p8')) {
        throw new \Exception('Failed to read PEM');
    }

    if (!openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256)) {
        throw new \Exception('Claims signing failed');
    }

    return $signature;
}

Base64 URL 编码...$data 具有预期的内容。

public function encode($data)
{
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}

在这一点上,我很困惑我做错了什么或错过了什么。我希望一些额外的眼睛会发现一些东西!使用我的代码转储出来的令牌:

curl  https://api.appstoreconnect.apple.com/v1/users --Header "Authorization: Bearer <token>”

...总是返回401。我怀疑代码的签名部分有问题,因为这是我唯一无法验证的部分(再次,在 Ruby 中工作),尽管查看了openssl_sign 的所有文档和示例,我很漂亮确定是对的。

作为参考,这是我提到的 Ruby 脚本https://shashikantjagtap.net/generating-jwt-tokens-for-app-store-connect-api/

【问题讨论】:

    标签: php jwt sha256 ecdsa app-store-connect


    【解决方案1】:

    经过大量测试,这对我有用:

    安装这个包:

    composer require lcobucci/jwt
    

    立即尝试生成令牌:

    <?php
    
    require 'vendor/autoload.php';
    
    use Lcobucci\JWT\Builder;
    use Lcobucci\JWT\Signer\Key;
    use Lcobucci\JWT\Signer\Ecdsa\Sha256; 
    
    $signer = new Sha256();
    $key = file_get_contents('AuthKey_xxxx.p8');
    
    $privateKey = new Key($key);
    $time = time();
    
    $token = (new Builder())->issuedBy('AppleTeamID') // Configures the issuer (iss claim)
        ->withHeader('alg', 'ES256')
        ->withHeader('kid', 'AppleKeyID')
        ->issuedAt($time) // Configures the time that the token was issue (iat claim)
        ->expiresAt($time + 1200) // Configures the expiration time of the token (exp claim)
        ->getToken($signer, $privateKey); // Retrieves the generated token
    
    // Test if your key works OK
    exec("curl -v -H 'Authorization: Bearer {$token}' \"https://api.music.apple.com/v1/catalog/us/artists/36954\"");
    
    
    exit;
    

    【讨论】:

    • 如果你把这个包含在 lcobucci/jwt v3 和 v4 中会很好,代码格式不同。
    【解决方案2】:

    经过一番苦苦挣扎,我终于用https://github.com/lcobucci/jwt让它工作了

    use Curl\Curl;
    use Lcobucci\JWT\Builder;
    use Lcobucci\JWT\Signer\Key;
    use Lcobucci\JWT\Signer\Ecdsa\Sha256;
    
    $signer = new Sha256();
    $privateKey = new Key('file://AuthKey_XYZ.p8');
    $time = time();
    
    $Issuer_ID = "FROM_APPLE_PAGE";
    $Key_ID = "FROM_APPLE_PAGE";
    
    $token = (new Builder())->issuedBy($Issuer_ID)// Configures the issuer (iss claim)
    ->permittedFor("appstoreconnect-v1")// Configures the audience (aud claim)
    ->identifiedBy('XXYYZZ', true)// Configures the id (jti claim), replicating as a header item
    ->withHeader('kid', $Key_ID)
    ->withHeader('type', 'JWT')
        ->withHeader('alg', 'ES256')
        ->issuedAt($time)// Configures the time that the token was issue (iat claim)
        ->expiresAt($time + 1200)// Configures the expiration time of the token (exp claim)
        ->withClaim('uid', 1)// Configures a new claim, called "uid"
        ->getToken($signer, $privateKey); // Retrieves the generated token
    
    
    $token->getHeaders(); // Retrieves the token headers
    $token->getClaims(); // Retrieves the token claims
    

    【讨论】:

      【解决方案3】:

      OpenSSL 返回的签名是包含附加信息的 ASN.1 序列。您必须在连接之前删除额外的数据。

      您可以使用 simple class I wrote(方法 fromDER)来转换 OpenSSL 签名。

      【讨论】:

      • 我对 OpenSSL 有点不适应,但你说的很有道理。我尝试使用您链接的代码,但得到 Invalid Length 异常。 return self::toDer($signature, 64); 我猜 $partLength 参数是错误的,但我传入的内容似乎并不重要。签名错误?
      • 对不起,我错过了指出要使用的函数是fromDER,而不是toDER
      • @Florent 你能举个例子吗?调用传递使用 OpenSSL 计算的签名的 fromDER 方法时出现 RuntimeException 错误。作为 $partLenght 值,我使用的是 64,但我不确定。
      • @FlorentMorselli 一个“Florent”可能没有看到问题,因为标记的名称无效
      【解决方案4】:

      您可以使用composer require firebase/php-jwt 来生成 JWT。

      首先,读取私钥文件。任何适合您的选择,freadfile_get_contentsSplFileObject 或来自 env 文件。只需获取.p8 文件的内容即可。

      那么,

      // $filePath = 'file:///var/www/html/AuthKey_KEY-ID-HERE-(JUST_IGNORE).p8'
      JWT::encode([
          'iss' => $teamId, // 10-character team id, under your name
          'iat' => $iat, // use strtotime('now') or Carbon::now()->timestamp
          'exp' => $exp, // use strtotime('+60 days') or Carbon::now()->days(60)->timestamp
          'aud' => "https://appleid.apple.com", // till date, it's constant
          'sub' => $sub, // Service ID identifier in https://developer.apple.com/account/resources/identifiers/list/serviceId, the id where you registered your "REDIRECT_URI"
      ], (new Key($filePath))->getContent(), 'ES256', $keyId); // Used Lcobucci\JWT\Signer\Key class as an experiment, and it also worked. You can you any of the above mentioned methods to get your key.
      

      运行此程序后,您将获得 client_secret 的客户端 ID。接下来,您可以使用文档。


      刚刚测试并得到了预期的结果。

      【讨论】:

        【解决方案5】:

        https://github.com/gradus0/appleAuth/blob/main/README.md

        <?php
        include_once "appleAuth.class.php";
        
        // https://developer.apple.com/account/resources/identifiers/list/serviceId -- indificator value
        $clientId = ""; // com.youdomen
        // your developer account id -> https://developer.apple.com/account/#/membership/
        $teamId = "";
        // key value show in -> https://developer.apple.com/account/resources/authkeys/list
        $key = ""; 
        // your page url where this script
        $redirect_uri = ""; // example: youdomen.com/appleAuth.class.php
        // path your key file, download file this -> https://developer.apple.com/account/resources/authkeys/list
        $keyPath =''; // example: ./AuthKey_key.p8 
        
            
        try{
            
            $appleAuthObj = new \appleAuth\sign($clientId,$teamId,$key,$redirect_uri,$keyPath); 
            
            if(isset($_REQUEST['code'])){
                $jwt_token = $appleAuthObj->get_jwt_token($_REQUEST['code']);
                $response = $appleAuthObj->get_response($_REQUEST['code'],$jwt_token);
                $result_token = $appleAuthObj->read_id_token($response['read_id_token']);
        
                var_dump($response);
                var_dump($result_token);
            }else{
        
                $state = bin2hex(random_bytes(5));
        
                echo "<a href='".$appleAuthObj->get_url($state)."'>sign</a>";
            }
                                                                                            
        } catch (\Exception $e) {
            echo "error: ".$e->getMessage();
        }
        

        【讨论】:

        • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
        猜你喜欢
        • 2022-11-30
        • 2020-11-16
        • 2020-05-01
        • 2018-07-13
        • 1970-01-01
        • 2016-04-24
        • 2016-07-31
        • 2023-03-25
        • 2017-07-30
        相关资源
        最近更新 更多