PHPでSAS tokenを使ってAzure Blob Storageにファイルをアップロードする
現在開発中の案件で、Shared Access Signature(SAS)を使ってBlobにデータを上げる必要があって、若干ハマったのでメモ。
先日プレビュー版がリリースされた user delegation SASってのもありますが、今回はストレージアカウントのキーを使う感じで。user delegation SASに関しては、亀淵さんのブログが参考になると思います。
今回使用するバックエンドはPHPなので、SASの作成には、microsoft/azure-storage-blobを使うことにします。
ざっとコード書くてみたのがこんな感じ...
<?php ... $accountName = getenv('STORAGE_ACCOUNT_NAME'); $accountKey = getenv('STORAGE_ACCOUNT_KEY'); $containerName = getenv('CONTAINER_NAME'); $helper = new BlobSharedAccessSignatureHelper($accountName, $accountKey); $sas = $helper->generateBlobServiceSharedAccessSignatureToken( Resources::RESOURCE_TYPE_BLOB, "{$containerName}/composer.json", 'w', Chronos::now(new \DateTimeZone('UTC'))->addSeconds(60)->format(DATE_ATOM), Chronos::now(new \DateTimeZone('UTC'))->subSeconds(10)->format(DATE_ATOM), '', // リクエスト元のIPアドレス 'https' ); echo "sas: {$sas}\n";
これで試すと、 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. こんなエラーがでてしまいます。
散々悩んだ挙げ句、ブラウザーで JavaScript と HTML を使用して BLOB をアップロード、一覧表示、および削除するを実際に動かして、リクエスト内容などを確認した結果ようやく原因がわかりました。
PHPの日付フォーマット DATE_ISO8601
がiso 8601準拠ではないので、 DATE_ATOM
を使っていたのですが、TimeZoneの書式が違うことが原因でした。
$ cat date.php <?php $date = new \DateTime('now', new \DateTimeZone('UTC')); echo $date->format(DATE_ISO8601)."\n"; echo $date->format(DATE_ATOM)."\n"; echo $date->format('Y-m-d\TH:i:s\Z')."\n"; ?> $ php date.php 2019-11-01T01:46:38+0000 2019-11-01T01:46:38+00:00 2019-11-01T01:46:38Z // -> この形式でないとだめ
'Y-m-d\TH:i:s\Z'
を使うことで正常にアップロードできました。めでたしめでたし。
最終的にできあがったサンプルがこちら
<?php require './vendor/autoload.php'; use Cake\Chronos\Chronos; use GuzzleHttp\Client; use MicrosoftAzure\Storage\Blob\BlobSharedAccessSignatureHelper; use MicrosoftAzure\Storage\Blob\Internal\BlobResources; use MicrosoftAzure\Storage\Common\Internal\Resources; $accountName = getenv('STORAGE_ACCOUNT_NAME'); $accountKey = getenv('STORAGE_ACCOUNT_KEY'); $containerName = getenv('CONTAINER_NAME'); $helper = new BlobSharedAccessSignatureHelper($accountName, $accountKey); $sas = $helper->generateBlobServiceSharedAccessSignatureToken( Resources::RESOURCE_TYPE_BLOB, "{$containerName}/composer.json", 'w', Chronos::now(new \DateTimeZone('UTC'))->addSeconds(60)->format('Y-m-d\TH:i:s\Z'), Chronos::now(new \DateTimeZone('UTC'))->subSeconds(10)->format('Y-m-d\TH:i:s\Z'), '', // リクエスト元のIPアドレス 'https' ); echo "sas: {$sas}\n"; try { $client = new Client(['base_uri' => sprintf('https://%s.%s/%s/', $accountName, Resources::BLOB_BASE_DNS_NAME, $containerName)]); $response = $client->put('composer.json', [ 'body' => file_get_contents('./composer.json'), 'headers' => [ 'x-ms-version' => BlobResources::STORAGE_API_LATEST_VERSION, 'x-ms-blob-type' => 'BlockBlob', ], 'query' => $sas, ]); echo sprintf("StatusCode: %d\n", $response->getStatusCode()); } catch(\Exception $e) { echo sprintf("StatusCode: %d\n", $e->getCode()); echo $e->getMessage() . "\n"; }
サンプルコードは、こちらにもあげてあります。