Подписывание URL-адресов с помощью JWT для облачного хранилища Google с использованием PHP


Я только что начал обновлять свой код облачного хранилища Google с версии API 1.0 до версии 2.0, и у меня возникли некоторые проблемы.

С версией 1.0 я с большим успехом использовал подписанные URL-адреса, используя файлы .p12. Однако в новой версии это устарело, и вместо этого я должен использовать Firebase/php-jwt, используя файлы JSON.

Проблема в том, что это просто не работает, я получаю ошибку:

<?xml version='1.0' encoding='UTF-8'?><Error><Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message>
<StringToSign>PUT

image/png
1483626991
/myBucket/folder/test.PNG</StringToSign></Error>

Это упрощенный код, используемый для его подписи.

$string = ($method . "\n" .
          $contentMd5 . "\n" .
          $contentType . "\n" .
          $expiration . "\n" .
          $file);

$signedURL = base64_encode(Firebase\JWT\JWT::encode($string,
        file_get_contents($credentialsFilePath)));

После Получен подписанный адрес, я создаю URL-адрес с правильными данными. Единственная часть, которую я изменил с 1.0 и 2.0, - это та часть, где вы подписываете URL-адрес. Кроме того, я проверил, что строка в поле "Stringtosign" ответа точно такая же, как та, которую я подписываю.

В версии 1.0 я подписал URL следующим образом:

$signedURL = base64_encode((new Google_Signer_P12(
        file_get_contents($p12FilePath),
        'notasecret'
      ))->sign($string));

Все это наводит меня на мысль, что я пою правильное содержание, но неправильно использую функцию JWT. Кто-нибудь еще делал это? Как ты справился это?

В случае, если это интересно, это URL, который я создаю (работает с 1.0):

$returnArr['url'] = "https://{$bucket}.commondatastorage.googleapis.com/"
    . $prefix . '/' . rawurlencode($file)
    . "?GoogleAccessId=" . rawurlencode($serviceEmail)
    . "&Expires={$expiration}"
    . "&Signature=" . rawurlencode($signature);
Author: Devon, 2017-01-05

1 answers

Глядя на источник для этой библиотеки JWT первое, что бросается мне в глаза, и я вижу, что было отмечено в комментариях, это то, что ваша полезная нагрузка должна быть массивом или объектом, а не строкой..."JSON веб-токены".

* @param object|array  $payload    PHP object or array

public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)

Во-вторых, похоже, что вы кодируете его двойным base64... base128?:) Возвращаемое значение encode должно быть тремя строками Base64url, соединенными вместе, поэтому вам не нужно будет делать это снова.

Я бы дал этому попробуйте:

$payload = ['HTTP_Verb'              => $method,
            'Content_MD5'            => $contentMd5,
            'Content_Type'           => $contentType,
            'Expiration'             => $expiration,
            'Canonicalized_Resource' => $file];

$key = file_get_contents($credentialsFilePath);
$signedURL = Firebase\JWT\JWT::encode($payload, $key); //think base64_encode here is redundant.

Ссылка: Обзор страницы подписанных URL-адресов . Они, конечно, не очень хорошо объясняют вещи в этих документах. Я полагаю, вы просмотрели SDK?

Если вы хотите пройти строковый маршрут, вам нужно будет подписать с помощью подписей RSA с помощью SHA256... opensssl_sign или также, возможно, проще опереться на PHP SDK от Google?

Позже...

Хорошо, решил проверить это. Видел, что у Google Cloud была бесплатная пробная версия. Установленный gsutil, прочитайте кучу документов. Будь я проклят, если я однако поймите этот подход JWT. Поделитесь, если кто-нибудь вообще может предоставить документы по этой теме.

Этот код работает:

<?php
$method = 'GET';
$expires = '1503532674';
$container = '/example-bucket/cat.jpeg';

$payload = "{$method}\n\n\n{$expires}\n{$container}";

//assume you have this 'json' formatted key too? Otherwise just load the private key file as is.
$key = file_get_contents('~/oas_private_key.json');
$key = json_decode($key, true);
$key = $key['private_key'];

//if sucessful the encypted string is assigned to $signature
openssl_sign($payload, $signature, $key, OPENSSL_ALGO_SHA256);

$signature = urlencode(base64_encode($signature));    

die("https://storage.googleapis.com/{$container}[email protected]&Expires={$expires}&Signature={$signature}");    

Наконец-то нет ошибки "SignatureDoesNotMatch"! Лично я бы использовал Пакет SDK. Немного инициализации, и вы можете просто сделать что-то вроде следующего:

$url = $object->signedUrl(new Timestamp(new DateTime('tomorrow')), [
    'method' => 'PUT'
]);

Это также упростило бы обновление в будущем.

 2
Author: ficuscr, 2017-08-25 03:25:37