如何使用 APNs Auth Key 和标准 CLI 工具发送 APNs 推送消息?

Posted

技术标签:

【中文标题】如何使用 APNs Auth Key 和标准 CLI 工具发送 APNs 推送消息?【英文标题】:How to send APNs push messages using APNs Auth Key and standard CLI tools? 【发布时间】:2016-10-09 12:36:50 【问题描述】:

Apple 最近向 APNS (Apple Push Notification Authentication Key (Sandbox & Production)) 添加了一种新的身份验证方法。

下载的密钥是一个带有私钥的.p8文件:

$ cat APNSAuthKey_3HHEB343FX.p8
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBH...Already.Revoked...lHEjCX1v51W
-----END PRIVATE KEY-----

我正在使用旧方法使用 APNs 消息 - 将它们添加到钥匙串,请求证书并使用 OpenSSL 将消息发送到 gateway.production.push.apple.com:2195

如何使用新格式的标准 CLI Linux 工具(OpenSSL、Python 等)发送推送通知?

【问题讨论】:

【参考方案1】:

如果您的机器上安装了支持 HTTP/2 的 curl 和支持 ECDSA 的 openssl,则可以使用以下脚本使用 APNs Auth Key 测试推送通知:

#!/bin/bash

deviceToken=b27371497b85611baf9052b4ccfb9641ab7fea1d01c91732149c99cc3ed9342f

authKey="./APNSAuthKey_ABC1234DEF.p8"
authKeyId=ABC1234DEF
teamId=TEAM123456
bundleId=com.example.myapp
endpoint=https://api.development.push.apple.com

read -r -d '' payload <<-'EOF'

   "aps": 
      "badge": 2,
      "category": "mycategory",
      "alert": 
         "title": "my title",
         "subtitle": "my subtitle",
         "body": "my body text message"
      
   ,
   "custom": 
      "mykey": "myvalue"
   

EOF

# --------------------------------------------------------------------------

base64() 
   openssl base64 -e -A | tr -- '+/' '-_' | tr -d =


sign() 
   printf "$1" | openssl dgst -binary -sha256 -sign "$authKey" | base64


time=$(date +%s)
header=$(printf ' "alg": "ES256", "kid": "%s" ' "$authKeyId" | base64)
claims=$(printf ' "iss": "%s", "iat": %d ' "$teamId" "$time" | base64)
jwt="$header.$claims.$(sign $header.$claims)"

curl --verbose \
   --header "content-type: application/json" \
   --header "authorization: bearer $jwt" \
   --header "apns-topic: $bundleId" \
   --data "$payload" \
   $endpoint/3/device/$deviceToken

注意:我在 ma​​cOS 上使用 homebrew 版本的 curl 和 openssl 进行测试:http://thrysoee.dk/apns/

Apple 现在有关于Send a Push Notification Using a Token 的this 方法的文档。

【讨论】:

@JessThrysoee 你的“轻微变化”就像一个冠军。感谢您将您的辛勤工作发布给所有人,是像您这样的人让社区充满活力 +1 感谢您提供这个非常有用的脚本。在我的 Mac 上,我必须添加“--with-nghttp2”选项来安装带有 http2 支持的 curl(因为 home-brew curl 目前似乎默认不支持 http2): brew install curl --with -nghttp2。 (或者因为我已经安装了 curl: brew reinstall curl --with-nghttp2 )然后在脚本中的 curl 命令上,我添加了选项: --http2 并且脚本运行良好。谢谢。 我不断收到“无法加载密钥文件” 请注意,如果使用 brew,则 curl 必须使用 OpenSSL 和 nghttp2 编译:brew install --with-openssl --with-nghttp2 curl @Neil 我已经用你的自制 curl-openssl 更改更新了 macOS 脚本。谢谢。【参考方案2】:

您可以通过 NODE JS 使用 Apple 推送通知身份验证密钥(沙盒和生产)发送推送通知。 this link有Elad Nava提供的教程

本教程包含创建 Apple 推送通知身份验证密钥和设置本地服务器以运行 Node JS 代码以发送推送通知的所有步骤。您可以在本地机器上运行代码并测试推送通知。

希望这会有所帮助。

【讨论】:

这是救生员!太优雅了!【参考方案3】:

瞧,php 中的 curl 和 HTTP/2 看起来像什么。该脚本返回 200 ok 状态码以及生成的令牌 ID。

// THE FINAL SCRIPT WITHOUT DEPENDENCIES!!! ...except curl with http2
$device_token = "a0abd886etc...";
//echo $key;
$kid      = "YOURKEYID";
$teamId   = "YOURTEAMID";
$app_bundle_id = "your.app.bundle";
$base_url = "https://api.development.push.apple.com";

$header = ["alg" => "ES256", "kid" => $kid];
$header = base64_encode(json_encode($header));

$claim = ["iss" => $teamId, "iat" => time()];
$claim = base64_encode(json_encode($claim));

$token = $header.".".$claim;
// key in same folder as the script
$filename = "KeyFromApple.p8";
$pkey     = openssl_pkey_get_private("file://$filename");
$signature;
openssl_sign($token, $signature, $pkey, 'sha256');
$sign = base64_encode($signature);

$jws = $token.".".$sign;

$message = '"aps":"alert":"You are welcome.","sound":"default"';

function sendHTTP2Push($curl, $base_url, $app_bundle_id, $message, $device_token, $jws) 

    $url = "$base_url/3/device/$device_token";
    // headers
    $headers = array(
        "apns-topic: $app_bundle_id",
        'Authorization: bearer ' . $jws
    );
    // other curl options
    curl_setopt_array($curl, array(
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
        CURLOPT_URL => $url,
        CURLOPT_PORT => 443,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => $message,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_SSL_VERIFYPEER => FALSE,
        CURLOPT_HEADER => 1
    ));
    // go...
    $result = curl_exec($curl);
    if ($result === FALSE) 
        throw new Exception("Curl failed: " .  curl_error($curl));
    
    print_r($result."\n");
    // get response
    $status = curl_getinfo($curl);
    return $status;

// open connection
$curl = curl_init();
sendHTTP2Push($curl, $base_url, $app_bundle_id, $message, $device_token, $jws);

【讨论】:

不客气。我很难让它在没有依赖关系的情况下工作。【参考方案4】:

Nicolas Manzini 提供的答案虽然很有帮助,但对我来说并不完全有效。我还想使用命令行 curl。特别是,读取 .p8 文件会导致一些问题。修改版:

<?php

$teamid = 'YOURTEAMID';
$keyid = 'A464FN6T93';     // in the name of the file downloaded from Apple Certs

// since it is a universal key, I wanted this in a location accessible to several accounts
$keyFile = '/path/to/file/AuthKey_A464FN6T93.p8';

$privateKey = openssl_pkey_get_private('file://' . $keyFile);

if (! $privateKey) 
    die('could not find: ' . $keyFile);


$header = ['alg' => 'ES256', 'kid' => $keyid];
$header = base64_encode(json_encode($header));

$claim = ['iss' => $teamid, 'iat' => time()];
$claim = base64_encode(json_encode($claim));

$tok = $header . '.' . $claim;

// pass in empty $signature, 2nd line below fills it
$signature = '';
$result = openssl_sign($tok, $signature, $privateKey, OPENSSL_ALGO_SHA256);      //  'sha256'
if (! $result) 
    die('unable to create signature');


$sign = base64_encode($signature);

openssl_free_key($privateKey);

$jwt = $tok . '.' . $sign;

foreach($tokens as $token) 
    $cmd = '\
    /usr/local/bin/curl -v \
    -d \'"aps":"alert":"title":"Notification Title","body":"You are being notified!","sound":"default"\' \
    -H "apns-topic: com.app.bundle.id" \
    -H "authorization: bearer ' . $jwt . '" \
    -H "content-type:application/json" \
    --http2 \
    https://api.push.apple.com/3/device/' . $token . ' 2>&1';    // ending w/ 2>&1, sends output to $output

    exec($cmd, $output, $return);

    if ($return != 0) 
        // be notified of error
    
    else 
        foreach($output as $line) 
            if (strpos($line, 'apns-id')) 
                $apns = $line;
            
        
    


?>

【讨论】:

如果我想向多个设备发送相同的消息,我可以使用令牌数组吗?

以上是关于如何使用 APNs Auth Key 和标准 CLI 工具发送 APNs 推送消息?的主要内容,如果未能解决你的问题,请参考以下文章

APNS auth key制作教程

Send Push Notifications to iOS Devices using Xcode 8 and Swift 3, APNs Auth Key

如何使用标准 C#.NET 发送带有“apns-expiration”标头的推送通知?

如何使用 vscode 和 Windows 10 中包含的 ssh-keygen 功能使用 ssh-key auth?

迁移到 iOS VoIP 推送通知

制作 .p12/.pem 并在本地测试 APNS 的正确方法