PHP的推送通知失败

时间:2019-09-30 01:36:55

标签: php push-notification jwt

我正在尝试将JWT推送通知发送到pushSubscription端点。 当我回显结果时,我得到“无效的JWT提供”,我不知道为什么它不起作用。我在php中生成ECDSA签名,并且返回的JWT在jwt.io中经过了有效测试。 注意:密钥不在生产中,而是提供给上下文的

/*GET ENDPOINT*/
$endpoint_push = json_decode($subscription)->endpoint;
$public_key_push = 'BNQCrj2wbXHBAK1hyjvc9R5zjypBwWG6szD_STnDPy2ORVUqTWZD304JS5LTHK5ywYS2w-aRouH3EjxLG9bWla8';

$token_push = '';




//PREPARE PUSH//

// Create token header as a JSON string
$header = json_encode(['typ' => 'JWT', 'alg' => 'ES256']);

// Create token payload as a JSON string
$payload = json_encode(['aud' => 'https://fcm.googleapis.com', 'exp' => '1516239022', 'sub' => 'mailto:push@example.com']);

// Encode Header to Base64Url String
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));

// Encode Payload to Base64Url String
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));


// Create Signature Hash

$privateKeyString =
"-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIHI6VMaMwvRag0foPp87+nhby3QrftcEsBHee6sdr0aZoAcGBSuBBAAK
oUQDQgAE91vCtp7tO4FyJbpgSS824PiuLR7LPNdwt+rcIe0uE19RUJz2Jgm8tRRD
HmBVzoQXNxcwVD1HfRMtU0wnUJOuAQ==
-----END EC PRIVATE KEY-----";

$privateKey = openssl_get_privatekey($privateKeyString);




$alg = OPENSSL_ALGO_SHA256;
$signature = null;

openssl_sign($base64UrlHeader . "." . $base64UrlPayload, $signature, $privateKey, $alg);




// Encode Signature to Base64Url String
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

// Create JWT
$token_push = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;







//SEND PUSH//
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $endpoint_push);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);

$headers = array();
$headers[] = 'Ttl: 60';
$headers[] = 'Content-Length: 0';
$headers[] = 'Authorization: vapid t='.$token_push.', k='.$public_key_push.'';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);

curl_close($ch);




echo json_encode($result);

1 个答案:

答案 0 :(得分:0)

因此,我基本上尝试了完全相同的事情,我花了一段时间才意识到OpenSSL将其大部分输出包装为ASN.1格式的结构,JWT令牌需要您首先剥离它。

这是一个工作脚本,希望其他人会觉得有用。

请注意,此脚本未将任何数据附加到推送消息中,因此Web客户端将收到没有上下文的推送消息,并且他们无法访问(据我所知)JWT对象二者之一,因此也不能在其中放置任何有用的数据。

当然有一种将加密数据附加到请求的方法,这里不介绍。

<?php

/*
 * If you get any part of this process wrong, Google gives the really helpful error message "invalid JWT provided".
 * 
 * Mozilla (Firefox) gives a slightly just-as-useful error:
 * {
 *   "code": 401, "errno": 109, "error": "Unauthorized",
 *   "more_info": "http://autopush.readthedocs.io/en/latest/http.html#error-codes",
 *   "message": "Request did not validate Invalid Authorization Header"
 * }
 */

// Generate the keys like this, although you can probably do it in PHP.
// `openssl ecparam -genkey -name prime256v1 -noout -out server-push-ecdh-p256.pem &>/dev/null`;
// `openssl ec -in server-push-ecdh-p256.pem -pubout -out server-push-ecdh-p256.pub &>/dev/null`;


$privk = file_get_contents('server-push-ecdh-p256.pem');
$pubk = file_get_contents('server-push-ecdh-p256.pub');
$endpoint = "https://fcm.googleapis.com/fcm/send/some-really-long-unique-secret-string-that-your-web-client-gets-on-push-subscribe";
$contact = "mailto:your-admin-email-address@example.com";

function base64web_encode($a) {
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($a));
}
function base64web_decode($a) {
    return base64_decode(str_replace(['-', '_', ''], ['+', '/', '='], $a));
}

$asn = new phpseclib\File\ASN1();

$header = [
    "typ" => "JWT",
    "alg" => "ES256"
];
$claims = [
    // just the https://hostname part
    "aud" => substr($endpoint, 0, strpos($endpoint, '/', 10)),
    // this push message will be discarded after 24 hours of non-delivery
    "exp" => time() + 86400,
    // who the server can talk to if our push script is causing problems
    "sub" => $contact
];

/*
 * Note these need to be base64 url-safe encoded, not standard base64.
 * @see https://tools.ietf.org/html/rfc4648#section-5
 */
$strHeader = base64web_encode(json_encode($header));
$strPayload = base64web_encode(json_encode($claims));

$toSign = $strHeader . '.' . $strPayload;

$signature = '';
if (!openssl_sign($toSign, $signature, $privk, OPENSSL_ALGO_SHA256)) {
    trigger_error('sign failed: '. openssl_error_string());
}

/*
 * openssl_sign produces a signature which is the hash wrapped in an 
 * ASN.1 structure, so we need to extract the 256-bit raw hash manually. 
 * There's no PHP function to do this, so we use a library.
 */
$xx = $asn->decodeBER($signature);
/** @var \phpseclib\Math\BigInteger $a */
/** @var \phpseclib\Math\BigInteger $b */
$a = $xx[0]['content'][0]['content']; // 128-bits
$b = $xx[0]['content'][1]['content']; // 128-bits
$signature = $a->toBytes() . $b->toBytes();
$strSignature = base64web_encode($signature);

/*
 * This is now a complete JWT object.
 */
$jwt = $strHeader . '.' . $strPayload . '.' . $strSignature;

/*
 * Our PEM formatted public key is wrapped in an ASN.1 structure, so just 
 * like our signature above, lets extract
 * the raw public key part, which is the bit we need.
 */
$xx = $pubk;
$xx = str_replace(['-----BEGIN PUBLIC KEY-----','-----END PUBLIC KEY-----',"\n"], '', $xx);
$xx = base64_decode($xx);
$xx = $asn->decodeBER($xx);
$xx = $xx[0]['content'][1]['content'];
$xx = substr($xx, 1); // need to strip the first char, which is not part of the key
$xx = base64web_encode($xx);
$pubkey = $xx;

/*
 * We need to append the public key used for signing this JWT object, so 
 * the server can validate the JWT and compare the public key against the 
 * push-registration by the client, where we said which public key we would 
 * accept pushes from.
 */
$headers = [
    "Authorization: vapid t=$jwt,k=$pubkey",
    "Content-length: 0",
    "Ttl: 86400",
];

/**
 * Push!
 */
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_exec($ch);
$ct = curl_multi_getcontent($ch);
echo curl_error($ch);
curl_close($ch);
echo $ct;