laravel8からs3へ画像アップロードしてみる。署名付きURL(期限付きURL)を生成して、S3に一時的に読み取り可能にする。
参考URL
https://qiita.com/nobu0717/items/51dfcecda90d3c5958b8
前回つくったのを改造する
やる事リスト
1, S3へのIAMユーザ作成
2, S3バケット作成
3, S3ポリシー設定(IAMユーザの許可)
4, laravelの.envに、IAMユーザのアクセスキーID・シークレットアクセスキー・S3バケット名を設定する。
5, 署名付きURL(期限付きURL)を生成して、非公開S3にアップロードしたファイルを一時的に読み取り可能にする。
1, IAMで、s3アクセス権限のあるユーザを作成(このユーザのアクセスキーを使ってlaravelから操作する)
1-a, s3_fullaccess_userとか適当なユーザ名にして、プログラムによるアクセスにチェック
1-b, AmazonS3FullAccessポリシーを付与
1-c, このユーザのアクセスキーIDとシークレットアクセスキーは、この時にしか取得できないので、CSVダウンロードしておく(もしくはどこかにメモしておく)
1-d, IAMユーザの管理画面から、s3_fullaccess_userの認証情報タブのコンソールのパスワードの管理リンクをクリック。有効化するとパスワードを取得できる。
1-e, 一旦ログアウトして、AmazonS3FullAccessと取得したパスワードでログインできるか確認する。
2, S3バケット作成
2-a, バケット名は全世界でユニークだから、image-uploader-英数乱数 みたいな感じで、乱数のプレフィックかサーフィックスを付与して作成する
2-b, すべてブロックしちゃうとlaravelからアクセスできないので、最初の2つはオフにする
新しいアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
任意のアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
2-c, バケット作成ボタンを押して完了
3, S3パケットポリシーで、s3_fullaccess_userをアスセス許可する
3-a, s3_fullaccess_userユーザーのARN(Amazon Resource Name)をIAM管理画面から取得
3-b, s3バケット(image-uploader-英数乱数)を開いて、プロパティタブからS3バケットのARNを取得
3-c,アクセス許可タブからパケットポリシーの編集ボタンを押す
3-d, 「ポリシージェネレータ」ボタンを押して、以下の値に設定。
「Select Type of Policy」: S3 Bucket Policy
「Effect」: Allow
「Principal」: コピーしたユーザのARN
「AWS Service」: Amazon S3
「Actions」:All Actions(‘*’)
「Amazon Resource Name(ARN)」:コピーしたバケットのARN
3-e, 「Add Statement」ボタンを押してから「Generate Policy」ボタンを押す。ポリシーのテキストが表示されるのでコピー
3-f, 元画面のパケットポリシーを編集のテキストエリアにコピペ。変更の保存ボタンを押してポリシー設定完了
4, laravelの.envに、IAMユーザのアクセスキーID・シークレットアクセスキー・S3バケット名を設定する。
AWS_ACCESS_KEY_ID= csvの認証情報に記載されているAccess key ID
AWS_SECRET_ACCESS_KEY= csvの認証情報に記載されているSecret access key
AWS_DEFAULT_REGION=ap-northeast-1 (リージョンをアジアパシフィック東京で作成したため)
AWS_BUCKET= 作成したバケット名
5, laravelからs3にアクセスするパッケージをcomposerでインストール
最初にlaravel用のawsパッケージをインストールする。
composer.jsonに追加して、composer updateする
1 2 3 |
"require": { "aws/aws-sdk-php-laravel": "~3.0", "php": "^7.3|^8.0", |
S3のパッケージをインストールする
1 2 |
# composer require league/flysystem-aws-s3-v2 composer require league/flysystem-aws-s3-v3:^1.0 |
6, /config/filesystems.phpを編集 laravel8では不要だった。
7, 画像ファイルのアップロード先をWebサーバからS3に変更する。
app/Http/Controllers/ImageController.phpを修正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
if ($request->file('file')->isValid([])) { // ファイルそのものはWebサーバに保存 $file_name = $request->file('file')->getClientOriginalName(); //$file_path = Storage::putFile('/uploads', $request->file('file'), 'public'); // $upload_image = $request->file('file'); // $file_path = $upload_image->store('uploads',"public"); // S3にアップロード(/uploadsフォルダ内に) $path = Storage::disk('s3')->putFile('/uploads', $request->file('file')); // アップロード先のURLを取得 $file_path = Storage::disk('s3')->url($path); // ファイル名とパスは、DBに保存する。 $image_info = new Image(); $image_info->file_name = $file_name; $image_info->file_path = $file_path; $image_info->file_comment = $request->input('file_comment'); $image_info->save(); } |
8, アップロードされた画像一覧を表示する部分を、S3のURLにする。
resources/views/image/index.blade.php
1 2 3 4 5 6 7 8 |
@foreach($images as $image) <div> <p>{{$image->created_at}} {{$image->file_comment}}</p> <a href="{{$image->file_path}}" target=blank> <img src="{{$image->file_path}}" width="240" alt="" title=""> </a> </div> @endforeach |
※ただし、S3ポリシーの問題で、ブラウザからS3オブジェクト(画像ファイル)へのURLを記述してもAccessDenyされる(画像が表示されない)
S3にアップロードされたファイルを、ネットに全公開する設定なら、ここまででOK。
9, 署名付きURL(期限付きURL)を生成して、非公開S3にアップロードしたファイルを一時的に読み取り可能にする。
S3は非公開がデフォなので、単純にアップロードしてもURLへアクセスできない。
なので、ただのURLではなく署名付きURL(期限付きURL)を生成してviewに渡す。
9-a, S3にアップロードした時に、URLではなく、S3キー(ファイルパス)をDBに保存するように変更
app/Http/Controllers/ImageController.phpを修正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public function store(Request $request) { ...... // S3にアップロード(/uploadsフォルダ内に)して、S3キー(ファイルパス)を取得 $file_path = Storage::disk('s3')->putFile('/uploads', $request->file('file')); // アップロード先のURLを取得 //$file_path = Storage::disk('s3')->url($path); // ファイル名とパスは、DBに保存する。 $image_info = new Image(); $image_info->file_name = $file_name; $image_info->file_path = $file_path; $image_info->file_comment = $request->input('file_comment'); $image_info->save(); } |
9-b, S3キー(ファイルパス)から、期限付きURLを取得する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class ImageController extends Controller { // S3キー(ファイルパス)から、期限付きURLを取得する public function GetPresignedURL(string $s3_key) { $s3 = Storage::disk('s3'); $client = $s3->getDriver()->getAdapter()->getClient(); $command = $client->getCommand('GetObject', [ 'Bucket' => env('AWS_BUCKET') , 'Key' => $s3_key , ]); $request = $client->createPresignedRequest($command, "+10 minutes"); return (string) $request->getUri(); } public function index() { // アップロードされた画像一覧を表示する $images = Image::all(); // S3キー(ファイルパス)から、期限付きURLを取得する foreach($images as $image){ $image['file_path'] = $this->GetPresignedURL($image['file_path']); } return view('image.index', compact('images')); } |
ブラウザの見た目は、何も変わってないけど、S3に保存できるようになった。
非公開S3から画像ファイルを取得するのに、署名付きURL(期限付きURL)を使ったのが、意外とサンプルがなくて苦労した。
ドットインストールの動画とかも、この方法で実装しているっぽい。
有効期限1分でもリロードすれば再発行されるので、問題ないはず…。と思ったけど、クリックして別タブで表示する時に有効期限が切れている可能性があるな…。
ログインのタイムアウトと同じにすべき?