【App Store Connect API ( 1 ) 】获取请求 API 的 Token

【App Store Connect API ( 1 ) 】获取请求 API 的 Token

Ty
Ty
2022-02-07 / 3 评论 / 157 阅读 / 正在检测是否收录...

前言

App Store Connect API 能自动完成您在 Apple DeveloperApp Store Connect 网站中执行的各种操作。

App Store Connect API 的调用需要 JSON Web Token(JWT) 进行授权。所以在调用之前,需要从组织的 App Store Connect 帐户中获取创建token的密钥。

创建密钥

打开并登录App Store Connect

选择 用户和权限 , 然后选择 API 密钥

点击 生成API密钥 或 + 按钮。

为密钥命名。

选择需要的权限。

点击 生成

创建密钥

点击 下载API密钥

将下载的文件妥善保存。

为 API 请求生成 Token

JSON Web Token(JWT) 是一个定义了安全传输信息的方法。 App Store Connect API 要求 JWT 对每个 API 请求授权。您创建了令牌后,需要使用从 App Store Connect 下载的私钥对其进行签名。

生成签名的JWT有三个步骤:创建JWT头、创建JWT有效负载、为JWT签名

以下是JWT头的示例。

{
    "alg": "ES256",
    "kid": "2X9R4HXF34",
    "typ": "JWT"
}

我们以 PHP 来举例。

下面是请求 Token 的代码。


<?php

error_reporting(0);

require './jwt.php'; //jwt.php就是对Token进行签名的代码

//kid => 密钥标识符
//iss => 发卡机构 ID
$iss=$_GET['iss'];
$kid=$_GET['kid'];

if(empty($iss)){
    $result = array(
        'status' => '409',
        'detail' => '$iss is required'
    );
    echo json_encode($result);
    exit();
}elseif(empty($kid)){
    $result = array(
        'status' => '409',
        'detail' => '$kid is required'
    );
    echo json_encode($result);
    exit();
}elseif(!file_exists('P8文件路径')){
    $result = array(
        'status' => '409',
        'detail' => 'cannot find p8 file in server, please upload your p8 file'
    );
    echo json_encode($result);
    exit();
}

$key = file_get_contents('P8文件路径');

$header = [
    'alg' => 'ES256',
    'kid' => $kid,
    'typ' => 'JWT',
];

$payload = [
    'iss' => $iss,
    'exp' => time() + 1200, //在 现在时间+1200 过期
    'aud' => 'appstoreconnect-v1'
];

$token =  ECSign::sign($payload, $header, $key);

$result = array(
    'status'=>'200',
    'expiration'=>time() + 1200,
    'token' => $token
);

echo json_encode($result);

exit();

下面是对 Token 进行 JWT签名 的代码。


<?php

class ECSign
{
    /**
     * sign
     * @param $payload
     * @param $header
     * @param $key
     * @return string
     * @throws Exception
     */
    public static function sign($payload, $header, $key)
    {
        $segments = [];
        $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
        $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
        $signing_input = implode('.', $segments);

        $signature = static::_sign($signing_input, $key);
        $segments[] = static::urlsafeB64Encode($signature);

        return implode('.', $segments);
    }

    /**
     * openssl_sign
     * @param $msg
     * @param $key
     * @return string
     * @throws Exception
     */
    private static function _sign($msg, $key)
    {
        $key = openssl_pkey_get_private($key);
        if (!$key) {
            throw new \Exception(openssl_error_string());
        }

        $signature = '';
        $success = openssl_sign($msg, $signature, $key, OPENSSL_ALGO_SHA256);
        if (!$success) {
            throw new \Exception("OpenSSL unable to sign data");
        } else {
            $signature = self::fromDER($signature, 64);
            return $signature;
        }
    }

    /**
     * jsonDecode
     * @param $input
     * @return mixed
     * @throws Exception
     */
    private static function jsonDecode($input)
    {
        if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
            $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
        } else {
            $max_int_length = strlen((string)PHP_INT_MAX) - 1;
            $json_without_bigints = preg_replace('/:\s*(-?\d{' . $max_int_length . ',})/', ': "$1"', $input);
            $obj = json_decode($json_without_bigints);
        }

        if (function_exists('json_last_error') && $errno = json_last_error()) {
            throw new \Exception(json_last_error_msg());
        } elseif ($obj === null && $input !== 'null') {
            throw new \Exception('Null result with non-null input');
        }
        return $obj;
    }

    /**
     * jsonEncode
     * @param $input
     * @return false|string
     * @throws Exception
     */
    private static function jsonEncode($input)
    {
        $json = json_encode($input);
        if (function_exists('json_last_error') && $errno = json_last_error()) {
            throw new \Exception(json_last_error_msg());
        } elseif ($json === 'null' && $input !== null) {
            throw new \Exception('Null result with non-null input');
        }
        return $json;
    }

    /**
     * urlsafeB64Decode
     * @param $input
     * @return false|string
     */
    private static function urlsafeB64Decode($input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $padlen = 4 - $remainder;
            $input .= str_repeat('=', $padlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    /**
     * urlsafeB64Encode
     * @param $input
     * @return mixed
     */
    private static function urlsafeB64Encode($input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
     * toDER
     * @param string $signature
     * @param int $partLength
     * @return string
     * @throws Exception
     */
    private static function toDER(string $signature, int $partLength): string
    {
        $signature = \unpack('H*', $signature)[1];
        if (\mb_strlen($signature, '8bit') !== 2 * $partLength) {
            throw new \Exception('Invalid length.');
        }
        $R = \mb_substr($signature, 0, $partLength, '8bit');
        $S = \mb_substr($signature, $partLength, null, '8bit');
        $R = self::preparePositiveInteger($R);
        $Rl = \mb_strlen($R, '8bit') / 2;
        $S = self::preparePositiveInteger($S);
        $Sl = \mb_strlen($S, '8bit') / 2;
        $der = \pack('H*',
            '30' . ($Rl + $Sl + 4 > 128 ? '81' : '') . \dechex($Rl + $Sl + 4)
            . '02' . \dechex($Rl) . $R
            . '02' . \dechex($Sl) . $S
        );
        return $der;
    }

    /**
     * toDER
     * @param string $der
     * @param int $partLength
     * @return string
     */
    private static function fromDER(string $der, int $partLength): string
    {
        $hex = \unpack('H*', $der)[1];
        if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
            throw new \RuntimeException();
        }
        if ('81' === \mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
            $hex = \mb_substr($hex, 6, null, '8bit');
        } else {
            $hex = \mb_substr($hex, 4, null, '8bit');
        }
        if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
            throw new \RuntimeException();
        }
        $Rl = \hexdec(\mb_substr($hex, 2, 2, '8bit'));
        $R = self::retrievePositiveInteger(\mb_substr($hex, 4, $Rl * 2, '8bit'));
        $R = \str_pad($R, $partLength, '0', STR_PAD_LEFT);
        $hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit');
        if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
            throw new \RuntimeException();
        }
        $Sl = \hexdec(\mb_substr($hex, 2, 2, '8bit'));
        $S = self::retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit'));
        $S = \str_pad($S, $partLength, '0', STR_PAD_LEFT);
        return \pack('H*', $R . $S);
    }

    /**
     * preparePositiveInteger
     * @param string $data
     * @return string
     */
    private static function preparePositiveInteger(string $data): string
    {
        if (\mb_substr($data, 0, 2, '8bit') > '7f') {
            return '00' . $data;
        }
        while ('00' === \mb_substr($data, 0, 2, '8bit') && \mb_substr($data, 2, 2, '8bit') <= '7f') {
            $data = \mb_substr($data, 2, null, '8bit');
        }
        return $data;
    }

    /**
     * retrievePositiveInteger
     * @param string $data
     * @return string
     */
    private static function retrievePositiveInteger(string $data): string
    {
        while ('00' === \mb_substr($data, 0, 2, '8bit') && \mb_substr($data, 2, 2, '8bit') > '7f') {
            $data = \mb_substr($data, 2, null, '8bit');
        }
        return $data;
    }
}
25

评论 (3)

取消
  1. 头像
    David Strangways
    Windows 7 · QQ Browser

    www.70pv.com-915515538@qq.com露娜luna神迹低价私服开区

    回复
  2. 头像
    Christiane Wise
    Windows 7 · QQ Browser

    www.41kv.com-1124999543@qq.com魔域传说ol低价私服开区

    回复
  3. 头像
    Emery Godinez
    Windows 8.1 · Google Chrome

    www.41mk.com-1325876192@qq.com仙境RO科洛斯低价私服开区

    回复