AzureBlobStorageに保存したリソースにPHPで署名付きURLを生成する方法

AzureBlobStorageとは?

AzureBlobStorageとはMicrosoftのクラウドプラットフォームAzureが提供するオブジェクトストレージサービスです。

「BLOB」は「Binary Large Object」の略で、非構造化データ(テキストやバイナリデータ)を保存するために最適化されています。

Azure Blob Storageは、画像、動画、バックアップファイル、ログファイル、データアーカイブなど、大量の非構造化データを効率的かつスケーラブルに保存、管理するために広く利用されています。

SASトークン(Shared Access Signatureトークン)について

Azureのストレージサービスに対する一時的かつ制限付きのアクセスを許可するための認証メカニズムです。

SASトークンを使うことで、特定のストレージリソース(Blob、ファイル、キュー、テーブルなど)へのアクセス権を外部のユーザーやアプリケーションに一時的に提供することができますが、通常はそのユーザーがAzureのストレージアカウント全体のアクセス権を持たない場合に利用されます。

ユーザー委任SASトークン

Azure Active Directory(AAD)のユーザー権限を使用して生成されるSASトークン。これにより、より高度なセキュリティを提供します。

アカウントSASトークン

ストレージアカウント全体に対してアクセスを許可します。リソースへの広範な操作(Blob、ファイル、キュー、テーブルサービスの全操作)が可能です。

サービスSASトークン

ストレージアカウントキーを使用して生成されるトークン。Blob、キュー、テーブル、ファイルに対する操作を許可するために使用されます。

署名付きURLとは?

「署名付きURL」(Signed URL)は、Azure Blob StorageやAWS S3といったクラウドストレージサービスで、特定のリソース(ファイルなど)に一時的にアクセスできるURLを生成する機能です。

署名付きURLは、アクセス制限されているファイルやオブジェクトに対して、認証されていないユーザーでも一時的にアクセスを許可するために使用されます。

署名付きURLの主な特徴

  1. 一時的なアクセス制御: 署名付きURLには有効期限があり、その期間が過ぎるとアクセスできなくなります。有効期限を設定することで、セキュリティを確保しながら一時的にファイルを共有できます。
  2. アクセス許可の範囲を設定可能: 署名付きURLには、アクセスの種類(例えば、ファイルの「ダウンロード」や「アップロード」など)を制御するオプションがあります。これにより、指定された操作だけが許可されます。
  3. 暗号化された署名: URLには、リソースへのアクセスを許可するための暗号化された署名が含まれており、この署名が有効である限りアクセスが許可されます。署名は、リソースの所有者が認証済みであることを保証します。

PHPでの実装方法

Azure公式のドキュメントを読んで理解できればいいのですが、、、Azureのドキュメントはそこまで親切なものでは無いので、自分でデバックしながら実装していく方が上手くいくと思います。

前提

  • Blobのアカウントは作成済みとします
  • アクセスレベルがprivateのblobストレージに保存されたリソースを対象とします
  • blobのアカウントキーは発行済みとします
  • 共有アクセス署名のサービスバージョンは「2020-04-08」を使用します

※下記ののサンプルコードは一例にすぎませんので、作成するクラス名等は「AzureBlobStorageに保存したリソースにPHPで署名付きURLを生成する方法」と直接的に関係するものではありません。

Storageの設定管理クラスの作成

class StorageManager
{
    public function __construct(
        protected readonly string $accountName,
        protected readonly string $accountKey,
        protected readonly string $containerName,
        protected readonly string $endpoint,
    ) {
    }

    /**
     * @param array $arraySign
     * @return string
     */
    public function makeSignature(array $arraySign): string
    {
        $str2sign = implode("\n", $arraySign);

        return base64_encode(hash_hmac('sha256', urldecode(utf8_encode($str2sign)), base64_decode($this->accountKey), true));
    }

    /**
     * @return string
     */
    public function getAccountName(): string
    {
        return $this->accountName;
    }

    /**
     * @return string
     */
    public function getContainerName(): string
    {
        return $this->containerName;
    }

    /**
     * @return string
     */
    public function getEndpoint(): string
    {
        return $this->endpoint;
    }
}

makeSignatureについて

署名文字列の作成の部分になります。引数で受け取っている要素(後ほど紹介します)を使ってAzureさん指定のやり方で署名文字列に変換しています。

base64_encode(hash_hmac('sha256', urldecode(utf8_encode($str2sign)), base64_decode($this->accountKey), true));

ServiceSASTokenを作成するクラスを実装する

重要なことはコメントで説明しています

<?php

/**
 * Azure Blob StorageのService SAS Tokenを生成するクラス
 */
class ServiceSASToken
{
    /** @var string */
    private readonly string $signedVersion;

    /** @var string */
    private readonly string $resource;

    /**
     * signedStartが指定されていない場合は、すぐに有効となる。
     * 閲覧の開始時間を指定する場合は、signedStartを指定する。
     * @param string $blobName
     * @param string $signedExpiry
     * @param string $permission
     * @param string|null $signedStart
     */
    public function __construct(
        private readonly string $blobName,
        private readonly string $signedExpiry,
        private readonly string $permission = 'r',
        private readonly ?string $signedStart = null
    ) {
        $this->signedVersion = '2020-04-08';

        // 「b」はBlobを指す
        $this->resource = 'b';
    }

    /**
     * @return string
     */
    public function token(): string
    {
        $signature = $this->makeSignature();

        $params = [
            'sv' => $this->signedVersion, // 共有アクセス署名のサービスバージョン
            'st' => $this->signedStart ?? '', // アクセス許可を開始する日時
            'se' => $this->signedExpiry, // アクセスを許可を終了する日時
            'sr' => $this->resource, // blobリソース名
            'sp' => $this->permission, // 許可する権限、「r」は読み取り権限
            'sig' => $signature, // 署名文字列
        ];

        // 署名付きURLを作成するリソースのURLに、クエリパラメータとして付与する必要がある
        return http_build_query($params);
    }

    /**
     * @return string
     */
    private function makeSignature(): string
    {
        $storageManager = new StorageManager();

     // canonicalizedResourceの構成は下記の通りです
     // 「/blob/blobアカウント名/blobコンテナ名/blobリソース名」
        $canonicalizedResource =
            "/blob/{$storageManager->getAccountName()}/{$storageManager->getContainerName()}/$this->blobName";

        $arraysign = [
            $this->permission, // signedPermission
            $this->signedStart ?? '', // signedStart
            $this->signedExpiry, // signedExpiry
            $canonicalizedResource, // canonicalizedResource
            '', // signedIdentifier
            '', // signedIP
            '', // signedProtocol
            $this->signedVersion, // signedVersion
            $this->resource, // signedResource
            '', // signedSnapshotTime
            '', // rscc
            '', // rscd
            '', // rsce
            '', // rscl
            '', // rsct
        ];

        return $accountManager->makeSignature($arraysign);
    }
}

blobのURLに署名を付与するクラスを作成する

<?php

use Illuminate\Support\Carbon;

class StampServiceSASToken
{
    /**
     * 引数で与えられたblobのURLに対して、
     * Azure Blob StorageのService SAS Tokenを付与する
     * @param string $blobUrl
     * @return string
     */
    public static function execute(string $blobUrl): string
    {
        // blob storageへのURLからblobリソース名のみを抽出します。
        $blobName = self::extractBlobName($blobUrl);
        $now = Carbon::now();

        // signedExpiryとsignedStartはUTC時間且つ、「Y-m-d\TH:i:s\Z」の形式にする必要があります
        $sas = new ServiceSASToken(
            blobName: $blobName,
            signedExpiry: $now->copy()->addMinute()->utc()
                ->format('Y-m-d\TH:i:s\Z'),
            permission: 'r',
            signedStart: $now->utc()
                ->format('Y-m-d\TH:i:s\Z'),
        );

     // 生成したsasトークンをblob urlにクエリパラメータとして付与する
        return $blobUrl.'?'.$sas->token();
    }

    /**
     * @param string $blobUrl
     * @return string
     */
    private static function extractBlobName(string $blobUrl): string
    {
        $storageManager = app(StorageManager::class);
        $extractStr = $storageManager->getEndpoint().'/'.$storageManager->getContainer().'/';

        return str_replace($extractStr, '', $blobUrl);
    }
}

上記の例ではsignedExpiryを現在時刻から1分後に設定しているため、1分間のみアクセス可能な状態になります。アクセス可能な時間を変更する場合は、このパラメータを変更すれば大丈夫です。

まとめ

署名付きURLは、特定のファイルやオブジェクトに一時的に安全にアクセスさせるための重要な機能です。ファイルをセキュアに扱いつつ、必要な時に必要な人にアクセスを許可する手段としては非常に有効な手法だと思います。

それにしても、Azureさんのドキュメントもう少し読みやすくしてほしいものですね。。。

AzureBlobServiceに保存したリソースの署名付きURLをPHPで生成する方法
最新情報をチェックしよう!