提问人:DrBrad 提问时间:11/17/2023 更新时间:11/18/2023 访问量:47
Web 推送通知 InvalidSignature
Web Push Notifications InvalidSignature
问:
无论我尝试什么,我似乎都无法获得推送通知......
这是错误代码。{"code":401,"errno":109,"error":"Unauthorized","message":"InvalidSignature","more_info":"http://autopush.readthedocs.io/en/latest/http.html#error-codes"}
该问题似乎与以太币、密钥不匹配或签名无效有关。
以下是我正在使用的一些资源: https://blog.mozilla.org/services/2016/08/23/sending-vapid-identified-webpush-notifications-via-mozillas-push-service/
https://autopush.readthedocs.io/en/latest/http.html#error-codes
https://datatracker.ietf.org/doc/rfc8292/
我正在生成公钥/私钥,如下所示:
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function generateVapidKeys(){
if(file_exists('vapid.json')){
$vapidKeys = json_decode(file_get_contents('vapid.json'));
return base64url_encode(hex2bin('04'.$vapidKeys->x.$vapidKeys->y));
}else{
$keyPair = openssl_pkey_new([
'digest_alg' => 'sha256',
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1', // P-256 curve
]);
}
$privateKeyDetails = openssl_pkey_get_details($keyPair);
$x = str_pad(bin2hex($privateKeyDetails['ec']['x']), 64, '0', STR_PAD_LEFT);
$y = str_pad(bin2hex($privateKeyDetails['ec']['y']), 64, '0', STR_PAD_LEFT);
$d = str_pad(bin2hex($privateKeyDetails['ec']['d']), 64, '0', STR_PAD_LEFT);
file_put_contents('vapid.json', json_encode([
'x' => $x,
'y' => $y,
'd' => $d,
], JSON_PRETTY_PRINT));
return base64url_encode(hex2bin('04'.$x.$y));
}
$publicKey = generateVapidKeys();
最后,这是我的通知发送:
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
function generate_jwt($headers, $payload, $privateKey){
$headers_encoded = base64url_encode(json_encode($headers));
$payload_encoded = base64url_encode(json_encode($payload));
//$signature = hash_hmac('SHA256', "$headers_encoded.$payload_encoded", $secret, true);
openssl_sign("$headers_encoded.$payload_encoded", $signature, $privateKey, OPENSSL_ALGO_SHA256);
$signature_encoded = base64url_encode($signature);
return "$headers_encoded.$payload_encoded.$signature_encoded";
}
function is_jwt_valid($jwt, $publicKey){
$tokenParts = explode('.', $jwt);
// check the expiration time - note this will cause an error if there is no 'exp' claim in the jwt
$expires = json_decode(base64_decode($tokenParts[1]))->exp < time();//($expires - time()) < 0;
$signature = openssl_verify($tokenParts[0].'.'.$tokenParts[1], base64_decode($tokenParts[2]), $publicKey, OPENSSL_ALGO_SHA256);
if($expires || !$signature){
return false;
}
return true;
}
function generateVapidToken($url, $privateKey) {
$expiration = time() + (12 * 60 * 60); // 12 hours
$header = [
'alg' => 'ES256',
'typ' => 'JWT',
];
$body = [
'aud' => $url,
'exp' => $expiration,
'sub' => 'mailto:[email protected]',
];
return generate_jwt($header, $body, $privateKey);
}
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// Assuming you have a database connection established
// Function to send a push notification
function sendPushNotification($subscription, $payload)
{
$parse = parse_url($subscription->endpoint);
$url = $parse['scheme'].'://'.$parse['host'];//.pathinfo(parse_url($parse['path'], PHP_URL_PATH))['dirname'];
echo $url.PHP_EOL.PHP_EOL;
$vapidKeys = json_decode(file_get_contents('vapid.json'));
//print_r(json_encode($vapidKeys, JSON_PRETTY_PRINT));
$keyPair = openssl_pkey_new([
'ec' => [
'digest_alg' => 'sha256',
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1', // P-256 curve
'x' => hex2bin($vapidKeys->x),
'y' => hex2bin($vapidKeys->y),
'd' => hex2bin($vapidKeys->d)
]
]);
$privateKeyDetails = openssl_pkey_get_details($keyPair);
openssl_pkey_export($keyPair, $privateKey);
$token = generateVapidToken($url, $privateKey);
//openssl_sign('HELLO WORLD', $signature, $privateKey, OPENSSL_ALGO_SHA256);
echo $token;
echo PHP_EOL;
echo PHP_EOL;
$publicKey = openssl_pkey_get_public($privateKeyDetails['key']);
$verified = is_jwt_valid($token, $publicKey);
//$verified = openssl_verify('HELLO WORLD', $signature, $publicKey, OPENSSL_ALGO_SHA256);
echo 'Token Valid: '.(($verified) ? "TRUE" : "FALSE");
echo PHP_EOL;
echo PHP_EOL;
$publicKey = base64url_encode(hex2bin('04'.$vapidKeys->x.$vapidKeys->y));
echo $publicKey;
echo PHP_EOL;
echo PHP_EOL;
$headers = [
//'Authorization: WebPush '.$token,
'Authorization: vapid t='.$token.',k='.$publicKey,
//'Authorization: key=' . $subscription->keys->auth,
//'Crypto-Key: p256ecdsa='.$publicKey.';dh='.$subscription->keys->auth,//$subscription->keys->p256dh,
'Content-Type: application/json',
];
/*
$notification = [
'title' => 'Your Notification Title',
'body' => 'Your Notification Body',
'icon' => 'path/to/icon.png',
];
*/
$data = [
'notification' => $payload,
//'applicationServerKey' => $vapidKeys->publicKey
];
$options = [
CURLOPT_URL => $subscription->endpoint,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
if ($result === false) {
echo 'Error: ' . curl_error($ch) . PHP_EOL;
} else {
echo 'Push notification sent successfully!' . PHP_EOL;
}
print_r($result);
}
// Example payload
$notificationPayload = [
'title' => 'New Notification',
'body' => 'This is the body of the notification.',
'icon' => 'icon.png'
];
if(file_exists('endpoints.json')){
$subscriptions = json_decode(file_get_contents('endpoints.json'));
// Send push notifications to all stored subscriptions
foreach ($subscriptions as $subscription) {
sendPushNotification($subscription, $notificationPayload);
}
}
?>
答:
1赞
Jakkapong Rattananen
11/17/2023
#1
你的程序是错误的。
- 密钥对 (vapid) 仅生成一次,但每次发送通知时都会生成。
- 我没有看到您的获取订阅代码,所以我想您手动模拟。我们不能这样做,因为我们只能从浏览器(通过 JavaScript)获取订阅。
为了使推送 API 正常工作,我们应该这样做。
- 生成密钥对保存到服务器上的安全位置。 需要在浏览器中创建订阅,您可以将它们保存在 javascript 代码或任何方法中。
private key
pubic key
- 创建订阅(由最终用户完成)并发送到服务器,将它们保存到数据库或任何存储中。
browser/user agent
- 发送通知创建包含(每个负载可能不同)遵循 RFC 标准并将它们发送到订阅中。
http request
notification payload
browser/user agent
endpoint
这里有一些资源
不要混淆推送 API 和通知 API。 这是不同的 API。我们可以推送,但不需要在通知 API 中使用推送的数据。因此,您可以从服务器推送任何数据并将它们转换为浏览器上的通知。
评论
0赞
DrBrad
11/18/2023
是的,我在问这个问题时被叮叮当当,因为我提供了太多的代码,哈哈。我只生成一次 VAPID 根据我的理解,我们有一个应用程序密钥对和一个用户密钥对,我们从订阅响应中获取用户密钥对?
0赞
Jakkapong Rattananen
11/18/2023
@DrBrad 是的,最终用户的密钥位于对象中,并且是二进制格式的应用程序示例。pushSubscription
applicationServerKey
public key
0赞
DrBrad
11/18/2023
#2
这个特定问题的答案是我生成 JWT 的方式。我需要以不同的方式修改签名。
function generate($headers, $payload, $privateKey){
$message = self::base64url_encode(json_encode($headers)).'.'.self::base64url_encode(json_encode($payload));
openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$components = [];
$pos = 0;
$size = strlen($signature);
while ($pos < $size) {
$constructed = (ord($signature[$pos]) >> 5) & 0x01;
$type = ord($signature[$pos++]) & 0x1f;
$len = ord($signature[$pos++]);
if ($len & 0x80) {
$n = $len & 0x1f;
$len = 0;
while ($n-- && $pos < $size) $len = ($len << 8) | ord($signature[$pos++]);
}
if ($type == 0x03) {
$pos++;
$components[] = substr($signature, $pos, $len - 1);
$pos += $len - 1;
} else if (! $constructed) {
$components[] = substr($signature, $pos, $len);
$pos += $len;
}
}
foreach ($components as &$c) $c = str_pad(ltrim($c, "\x00"), 32, "\x00", STR_PAD_LEFT);
return $message . '.' . self::base64url_encode(implode('', $components));
//return "$headers_encoded.$payload_encoded.$signature_encoded";
}
评论