はじめに
Laravel で S3 の名付き URL を取得する API を実装したので、その手順をまとめておこうと思う。
署名付き URL とは
通常、アクセス制限されている S3 のバケットのオブジェクトを操作しようとすると、権限が必要がある。1
署名付き URL は、一言で言うと 通常必要な認証なしに特定の S3 オブジェクトに一時的にアクセスすることができる URL である。署名付き URL を発行するには、当該 S3 バケットあるいはオブジェクトへの権限を持つユーザあるいはリソースが任意の有効期限を定めて作成する必要がある。
方法としては、REST API, AWS CLI, AWS SDK の 3 パターンで実現することができる。この辺はさすが AWS と言うところで全てのインターフェースから抵抗なく呼び出すことができるらしい。
今回は、3 つ目の AWS SDK for PHP 2 を使って Laravel で構築したアプリケーション上に実装することにした。
実装
routes/api.php
まず、API のエンドポイントをルーティングに定義する。
Route::get('s3/presignedurl', 'S3ClientController@getPresignedUrl')->name('s3.getPresignedUrl');
app/Http/Controllers/S3ClientController.php
次に、ルーティングに定義したコントローラにアクションを作成していく。
use App\Http\Requests\GetPresignedUrlRequest;
use Aws\S3\S3Client;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
/**
* @param \App\Http\Requests\GetPresignedUrlRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function getPresignedUrl(GetPresignedUrlRequest $request): JsonResponse
{
$s3Client = new S3Client([
'region' => config('filesystems.disks.s3.region'),
'version' => config('filesystems.disks.s3.aws_sdk_version'),
'endpoint' => config('filesystems.disks.s3.client_url')
]);
$filename = $this->makeUniqueFilename(
pathinfo($request->filename, PATHINFO_EXTENSION)
);
$cmd = $s3Client->getCommand($request->method), [
'Bucket' => config('filesystems.disks.s3.bucket'),
'Key' => $filename
]);
$presignedRequest = $s3Client->createPresignedRequest(
$cmd,
config('filesystems.disks.s3.aws_sdk.pre_signed_url.expired_time')
);
return response()->json([
'filename' => $filename,
'pre_signed_url' => (string) $presignedRequest->getUri()
]);
}
/**
* @param string $extension
* @return string
*/
public function makeUniqueFilename(string $extenstion): string
{
return (string) Str::uuid() . '.' . $extension;
}
順序立てて説明する。
getPresignedUrl
と言うメソッドでは GetPresignedUrlRequest
というフォームリクエスト 3 を経由するようにしており、そうすることでコントローラ内にバリデーションを実装することなく処理を簡潔に書くことができる。
app/Http/Requests/GetPresignedUrlRequest.php
下記では、 filename
, method
というオプションは必須で文字列でないとバリデーションエラーになるようにルールを指定した。
今回はアップロードなので method
は 'PUT'
を指定する必要があるが、オブジェクトを取得する際は 'GET'
を指定すれば問題ない。
public function rules()
{
return [
'filename' => 'bail|string|required',
'method' => 'string|required'
];
}
次に、 config()
と何箇所か書いているところについて。
Laravel では、コントローラなどの処理に直接秘密情報である .env の情報を書くことは御法度とされている。だったらどうするかと言うと config/*.php
内で .env の値を取得し、コントローラでは config から読み取るという手法を取っている。4
今回は、デフォルトである filesystems.php
に書いてみた。
config/filesystems.php
return [
// 省略
'disks' => [
// 省略
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-west-2'),
'bucket' => env('AWS_BUCKET', 'test-bucket'),
'aws_sdk' => [
'version' => 'latest',
'pre_signed_url' => [
'expired_time' => '+5 minutes'
]
],
'client_url' => env('CLIENT_URL')
]
]
];
最後に、makeUniqueFilename
メソッドについて。
これはおまけみたいなものだが、uuid にファイル名を整形する処理を挟んでみた。S3 バケット内のオブジェクトを一意に識別できるため、こうしておくことにした。
余談だが、S3 はオブジェクトを一位に識別するために 16 桁のプレフィックスを付けることを以前は推奨されていたが昨年くらいのアップデートによりその制限はなくなっている。とは言え、同じファイル名でアップロードされてしまうとオブジェクトを更新しかねないのでオブジェクト名は気を付けておいた方が良い。
確認してみる
上で実装したエンドポイントに対してアクセスし、次のようなレスポンスが返って来たら OK。
{
"filename": "cb12afe2-4e70-1234-5678-c248f4d1213b.jpeg",
"pre_signed_url": "https://test-bucket.s3.us-west-2.amazonaws.com/cb12afe2-4e70-1234-5678-c248f4d1213b.jpeg?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA1AB234ABCDE5FGHIJ%2F20200719%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200722T103113Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Signature=123456789abc10ef203a46c75ff0e89c5678fd5f69a12345ee6d608123456cbd"
}
ここまで確認できれば、filename
のファイル名で pre_signed_url
に対してアップロードすれば private ACL に設定されているバケットにもアップロードできる。
まとめ
Laravel で S3 の署名付き URL を取得する API を実装してみた。
これまでは、サーバサイド側でアップロードの処理を実装していたのが、Pre-Signed URL を取得することでフロントエンドからでもアップロードが可能になった。より実装の自由度が高くなったことで、アーキテクチャを疎結合化できたり、 UX を向上させることができたりすると良い。
参考にさせていただいた記事
- 署名付き URL を使用したオブジェクトのアップロード - Amazon Simple Storage Service
- 【AWS S3】S3 Presigned URLの仕組みを調べてみた - Qiita
-
必要な権限が付与されている IAM アカウント、もしくは IAM ロールがアタッチされているかどうか。
↩ -
AWS SDK for PHP バージョン 3 での Amazon S3 の署名付き URL - AWS SDK for PHP
↩ -
フォームリクエストとは、HTTP リクエストをバリデーションする便利な機能です。下記の記事にわかりやすく解説してありました。
↩
Laravelのバリデーションにはフォームリクエストを使おう - Qiita -
理由については、下記の記事にわかりやすく解説してありました。
↩
Laravel では env() を config 系ファイル以外の場所に書いてはいけない - Qiita