【问题标题】:Send Push with .p8 and PHP使用 .p8 和 PHP 发送推送
【发布时间】:2019-08-22 11:27:00
【问题描述】:

我需要使用 .p8 文件通过 PHP 脚本发送推送通知,并在 here 提出的类似问题中找到以下代码。

<?php

  $keyfile = 'AuthKey_AABBCC1234.p8';               # <- Your AuthKey file
  $keyid = 'AABBCC1234';                            # <- Your Key ID
  $teamid = 'AB12CD34EF';                           # <- Your Team ID (see Developer Portal)
  $bundleid = 'com.company.YourApp';                # <- Your Bundle ID
  $url = 'https://api.development.push.apple.com';  # <- development url, or use http://api.push.apple.com for production environment
  $token = 'e2c48ed32ef9b018........';              # <- Device Token

  $message = '{"aps":{"alert":"Hi there!","sound":"default"}}';

  $key = openssl_pkey_get_private('file://'.$keyfile);

  $header = ['alg'=>'ES256','kid'=>$keyid];
  $claims = ['iss'=>$teamid,'iat'=>time()];

  $header_encoded = base64($header);
  $claims_encoded = base64($claims);

  $signature = '';
  openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
  $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);

  // only needed for PHP prior to 5.5.24
  if (!defined('CURL_HTTP_VERSION_2_0')) {
      define('CURL_HTTP_VERSION_2_0', 3);
  }

  $http2ch = curl_init();
  curl_setopt_array($http2ch, array(
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
    CURLOPT_URL => "$url/3/device/$token",
    CURLOPT_PORT => 443,
    CURLOPT_HTTPHEADER => array(
      "apns-topic: {$bundleid}",
      "authorization: bearer $jwt"
    ),
    CURLOPT_POST => TRUE,
    CURLOPT_POSTFIELDS => $message,
    CURLOPT_RETURNTRANSFER => TRUE,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HEADER => 1
  ));

  $result = curl_exec($http2ch);
  if ($result === FALSE) {
    throw new Exception("Curl failed: ".curl_error($http2ch));
  }

  $status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
  echo $status;

  function base64($data) {
    return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
  }

?>

但是,我发现openssl_pkey_get_private 没有读取密钥文件,并且在调试时出现以下错误。

$key = openssl_pkey_get_private('file://'.$keyfile);
if ($key === false) {
    var_dump(openssl_error_string());
}

错误:

'error:02001002:system library:fopen:No such file or directory'

请注意curl 没有问题,因为HTTP2 已为curl 启用,而我正在使用PHP7。在测试阶段,我使用同一文件夹中的脚本和文件来避免任何路径问题。

任何线索哪里出错了?

【问题讨论】:

  • $keyfile 的内容是什么?您可以从终端实际打开该文件吗?它似乎不存在,因此抛出“没有这样的文件或目录”错误。
  • 是的,我可以打开它。我将 php 脚本和 .p8 文件放在同一个文件夹中,并且在给 file:// 时没有指定任何路径。
  • 您使用的是什么操作系统?基于unix的区分大小写。我试过你的代码,它工作正常:它抛出 string(50) "error:0909006C:PEMroutines:get_name:no start line" 而不是找不到文件错误
  • 我的错,我没有提到它。我在 Windows 10 上使用 wamp 服务器。
  • 我尝试使用浏览器而不是通过终端复制它。如果通过浏览器访问,则需要提供绝对路径。像这样:openssl_pkey_get_private('file:///C:/xampp/htdocs/'.$keyfile);

标签: php openssl apple-push-notifications p8


【解决方案1】:

如果还没有阅读,请参考下一个网址。

https://www.php.net/manual/en/function.openssl-pkey-get-private.php

要缩小您的问题范围,请为您的 php 文件和密钥文件使用相同的目录并尝试此工作代码。

工作代码

$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absolute path
$key = openssl_pkey_get_private($keyfile);

if ($key === false) {
    var_dump(openssl_error_string());
}else{
    var_dump($key);
}

以下可能是个问题。

  1. 路径

以下路径样式应该可以工作。

$keyfile="file:///home/john/php/key.p8"; // unix absoulute path
$keyfile="file://C:\\users\\john\\php\\key.p8"; // windows absoulute path
$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absoulute path for unix, windows
$keyfile="file://key.p8"; // relative path, unix, windows, (php,key files in same directory)

$key = openssl_pkey_get_private($keyfile);

如果路径不存在,错误会是这样的

"error:02001002:system library:fopen:No such file or directory"

  1. 网络环境

    检查您的网络根和网络用户对文件夹和密钥文件的访问权限。

    为了减少问题,请在 php 内置 Web 服务器环境而不是 WAMP 环境上进行测试。

>php -S localhost:80
  1. 密钥文件损坏

保存为包含空格的特定类型。 这可能会发生类似下一个错误。

“错误:0906D06C:PEM 例程:PEM_read_bio:no start line”

in my case, key file was saved as UTF-8 with BOM(whitespaces)

调试密钥文件 1 - 从变量读取

这段代码应该可以工作。我从 http://micmap.org/php-by-example/en/function/openssl_pkey_get_private

请将 $str 替换为您的。

$str = <<<EOF
-----BEGIN RSA PRIVATE KEY----- 
MIIEogIBAAKCAQEA0llCeBjy18RylTdBih9GMUSZIC3GzeN0vQ9W8E3nwy2jdeUn 
H3GBXWpMo3F43V68zM2Qz5epRNmlLSkY/PJUfJIC8Yc1VEokT52q87hH/XJ5eS8h 
eZnjuSlPAGi8oZ3ImVbruzV7XmlD+QsCSxJW7tBv0dqJ71e1gAAisCXK2m7iyf/u 
l6rT0Zz0ptYH4IZfwc/hQ9JcMg69uM+3bb4oBFsixMmEQwxKZsXk3YmO/YRjRbay 
+6+79bSV/frW+lWhknyGSIJp2CJArYcOdbK1bXx1dRWpbNSExo7dWwuPC0Y7a5AE 
eoZofieQPPBhXlp1hPgLYGat71pDqBjKLvF5GwIDAQABAoIBACPItYsSy3UzYT7L 
OKYTrfBBuD8GKpTqBfkHvAWDa1MD15P92Mr7l0NaCxGfAy29qSa6LdFy/oPM9tGY 
9TxKyV6rxD5sfwEI3+Z/bw6pIe4W5F1eTDaQnHHqehsatkRUQET9yXp+na8w/zRF 
0C0PQKS95tfvcpm59RGCdGQ8+aZw+cIy/xez75W8IS/hagMxe7xYPjpkOkSCCEJU 
zmbVq6AyWodASV0p4H9p8I+c0vO2hJ/ELJ167w6T+2/GlZg979rlyHoTW8jK2BbG 
IRGaPo+c2GANXa686tdpbkPd6oJliXwBSNolxmXShvlveBbPFAJJACzCmbXNj9kH 
6/K+SWkCgYEA7FNudcTkRPV8TzKhJ1AzDjw3VcnraYhY8IlNxbk7RVHLdkoUtwk/ 
mImeBlEfCoz9V+S/gRgeQ+1Vb/BCbS24+bN/+IGoNRFMRcOieFt6lQUpj7a9NeSo 
IEclGgUiU7QR3xH73SB4GC3rgSPeHJhJZC5EJq5TzYjXTPGPpBD3zicCgYEA49wz 
zfMDYIH8h4L65r/eJYIbLwpvgktgaYvhijO3qfZSWW+Y19jCBn55f65YOhPGQBHA 
my0f+tVxFNZ/OupbrAIIzogxlCIYHNBawDhoHN/sB3/lSBAjifySNLyRlA62oA0w 
wXvXVLVWMa3aXim3c9AlnLF1fHwcvwpOKSfdye0CgYBb1mBKq+T5V1yjek1d9bCh 
i40FbZ5qOG43q2Ppvn3mBk9G/KroJlPsdy5NziB9/SRGj8JL7I92Xjihc4Cc5PPJ 
NZQ5gklXtg0p30i39PTCDGuGScFlvCIJyRwF7JDWblezlE2INSH2Y4HtgX7DJfr/ 
T2t0jLJMYS0p3YWwgFeMaQKBgHUIe/8y6zAdc5QynSX5tGL1gXrW1FFK39k2RICU 
cag1YTSYkhuDNJzbRxJifORPlcsAkzngooVWLb+zMCQVjUI6xUU3RKe+Hz5lccc6 
8ZarGHL9qMkrqOVNudamZ+tw5zIrtDgcoIvcm8nmbrtgl94/MaJar2ph4O3qoByZ 
Ylw9AoGAIdS79s0VKkj4VVXqK47ZcI7jGL4V4C8ujU8YcMNV88xwCoDg9ZIFprWA 
P5p/cnvj6aHnqL58XiH0+bE0Lt3J+U6N6JelQQevgBHooMFh4FpDXcVda7xB3rK3 
woqbi8fNhr827H2maxIZPtVG95/mvR4k5z1Jrdnr34ZUmtC6U5Q= 
-----END RSA PRIVATE KEY-----
EOF;

$key = openssl_pkey_get_private($str);
if ($key === false) {
    var_dump(openssl_error_string());
}else{
    var_dump($key);
}

输出

resource(4) 类型(OpenSSL 密钥)

调试密钥文件 2 - 从文件中读取

将您的密钥字符串($str) 复制到“key.p8”之类的密钥文件中。

$str = <<<EOF
-----BEGIN RSA PRIVATE KEY----- 
...YOUR KEY STINGS HERE...
-----END RSA PRIVATE KEY-----
EOF;

$str2 = file_get_contents("key.p8");

$len1 = strlen ($str);
$len2 = strlen ($str2);
if($len1 !== $len2) echo "File has been corrupted.";

$key = openssl_pkey_get_private($str2);

if ($key === false) {
    var_dump(openssl_error_string());
}else{
    var_dump($key);
}

【讨论】:

    【解决方案2】:

    此脚本可用于使用 .p8 证书向 IOS 发送推送。 确保证书的位置正确

    
    <?php
    
          $keyfile = 'AuthKey_AABBCC1234.p8';               // Your p8 Key file
          $keyid = 'AABBCC1234';                            // Your Key ID
          $teamid = 'AB12CD34EF';                           // Your Team ID (see Developer Portal)
          $bundleid = 'com.company.YourApp';                // Your Bundle ID
          $url = 'https://api.development.push.apple.com';  // development url, or use http://api.push.apple.com for production environment
          $token = 'e2c48ed32ef9b018........';              // Device Token
    
          $message = '{"aps":{"alert":"Hi there!","sound":"default"}}';
    
          $key = openssl_pkey_get_private('file://'.$keyfile);
    
          $header = ['alg'=>'ES256','kid'=>$keyid];
          $claims = ['iss'=>$teamid,'iat'=>time()];
    
          $header_encoded = base64($header);
          $claims_encoded = base64($claims);
    
          $signature = '';
          openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
          $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);
    
          // only needed for PHP prior to 5.5.24
          if (!defined('CURL_HTTP_VERSION_2_0')) {
              define('CURL_HTTP_VERSION_2_0', 3);
          }
    
          $http2ch = curl_init();
          curl_setopt_array($http2ch, array(
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
            CURLOPT_URL => "$url/3/device/$token",
            CURLOPT_PORT => 443,
            CURLOPT_HTTPHEADER => array(
              "apns-topic: {$bundleid}",
              "authorization: bearer $jwt"
            ),
            CURLOPT_POST => TRUE,
            CURLOPT_POSTFIELDS => $message,
            CURLOPT_RETURNTRANSFER => TRUE,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HEADER => 1
          ));
    
          $result = curl_exec($http2ch);
          if ($result === FALSE) {
            throw new Exception("Curl failed: ".curl_error($http2ch));
          }
    
          $status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
          echo $status;
    
          function base64($data) {
            return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
          }
    
        ?>```
    

    【讨论】:

    • 这是问题中的完全相同的代码。为什么会被赞成?
    • 如果我想向多个设备发送相同的消息,我可以使用令牌数组吗?
    猜你喜欢
    • 2018-10-24
    • 2019-08-29
    • 2015-08-19
    • 2014-12-22
    • 1970-01-01
    • 2018-12-17
    • 2017-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多