Подписание URL-адресов с помощью JWT для Google Cloud Storage с использованием PHP

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

С версией 1.0 я пользовался Signed URLs с большим успехом, используя файлы .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 я создаю 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);

Ответы

Ответ 1

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

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

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

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

Я бы попробовал:

$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, а также, может быть, проще опираться на Google PHP SDK?

Позже...

ОК, решил проверить его. У 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'
]);

Это также облегчит обновление в будущем.