LaravelでAmazon S3にファイルをアップロードする【Larvel】【Amazon S3】

今回はLaravelアプリケーションからAmazon S3にファイルをアップロードして利用する手順についてです。
レコードの登録時にファイルのアップロードを伴うこともあると思います。その際に利用できる手法の1つです。
今回はファイルのアップロードとDBへの値の保存、画面上への表示について考えていきます。

Laravelアプリケーションの設定

AWS SDK for PHPのインストール

まずはLaravelとS3を連携に必要なパッケージをインストールします。

composer require league/flysystem-aws-s3-v3

設定項目の追加

続いて認証情報などをアプリケーション設定に記述します。
今回は.envに記述します。configなどに直接書くことも可能ですが、環境によって変更が必要な設定については.envに記述して環境によって容易に変更できるようにしておくと良いと思います。.envの下記項目を探して値を設定しましょう。

AWS_ACCESS_KEY_ID=あなたのアクセスキー
AWS_SECRET_ACCESS_KEY=あなたのシークレットキー
AWS_DEFAULT_REGION=あなたのリージョン
AWS_BUCKET=あなたのバケット名

config/filesystems.phpに以下の記述が含まれることを確認しましょう。

    'disks' => [
        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),
            'endpoint' => env('AWS_ENDPOINT'),
            'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
            'throw' => false,
        ],
    ],

設定項目で必要な情報について

AWSのバージョンなどで微妙に設定の仕方が異なると思いますが、執筆時点(2024/1/3)での情報の取得について記述します。
セキュリティのことを考えてスクリーンショットは少なめなので、キーワードをベースにマネジメントコンソールで作業を行ってください。

バケット情報

ファイルを保存するためのバケットを作成して、アクセス管理とバケット情報を取得します。

  1. AWSのサービスからAmazon S3にアクセスする
  2. バケットの作成からバケットの作成を行う
    細かい設定は後々行っていくので、ここではリージョンとバケット名を設定し、他はデフォルト設定のままバケットを作成
  3. 作成後、バケット一覧に遷移した場合はバケット名をクリックしてバケット情報を開く
  4. プロパティからリージョン名やリソースネームを確認できます。
アクセスキーとシークレットキー

IAMユーザーを作成してキーを取得します。

ポリシーの作成

まずはポリシーを作成します。後述のIAMユーザーに許可するアクセスを定義するものです。
様々な記事で「AmazonS3FullAccess」を選択すると紹介されていますが、FullAccessの名前の通り権限を付与しすぎるので自分で定義していきますこちらを参考にしました。

  1. AWSのサービスからIdentity and Access Management (IAM)にアクセスする
  2. アクセス管理 > ポリシー > ポリシーの作成 からポリシーの作成を行う
  3. ポリシーエディタの「JSON」をクリックして、JSONエディタを開く
  4. 内容を後述のJSONに変更して次へ
    JSON内の「your-bucket-name」は先ほど確認したバケット情報で書き換えが必要
  5. 名前を設定してポリシーを作成する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:GetObjectAcl",
                "s3:PutObjectAcl",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::your-bucket-name",
                "arn:aws:s3:::your-bucket-name/*"
            ]
        }
    ]
}
IAMユーザーの作成

続いてIAMユーザーを作成し、アクセス情報を取得します。

  1. AWSのサービスからIdentity and Access Management (IAM)にアクセスする
  2. アクセス管理 > ユーザーからユーザーの新規追加を実施
  3. ユーザー名は自由に設定し、許可のオプションでは「ポリシーを直接アタッチする 」から先ほど作成したポリシーを選択
    絞り込みタイプを「カスタマー管理」とすることで探しやすくなります
  4. 作成後ユーザー名をクリックし、ユーザーの設定画面を開く
  5. セキュリティ認証情報 > アクセスキーを作成 からアクセスキーを作成する
    ※「主要なベストプラクティスと代替案にアクセスするの選択」が必要な場合、実行環境にもよりますが、AWS外からのアクセスの場合は「AWS の外部で実行されるアプリケーション」となると思います
  6. アクセスキー作成後、アクセスキーとシークレットアクセスキーが表示されるのでメモしておきましょう
    .csvファイルをダウンロードからダウンロードも可能です

動作チェック

正しく動作するかを確認していきます。ここは飛ばしても問題ありませんが、この後のプログラミングが問題なのか、ここまでの設定周りが問題なのかの調査がめんどくさいことになる可能性があるので、単純処理で問題なくアップロードできることを確認しておきましょう。

適当なコントローラーに以下の処理を追加して処理を実行します。

use Illuminate\Support\Facades\Storage;

// ファイルをアップロードする
// ファイルをアップロードする
$fileContents = Storage::get('test.png');
Storage::disk('s3')->put('test.png', $fileContents);
$path = Storage::disk('s3')->temporaryUrl('test.png', now()->addMinutes(5));

diskで何も設定しない場合、config/filesystems.phpのdefaultで設定されているdiskが選択されます。
私の実行環境の場合、defaultがlocalとなっているため、上記ソースでは「storage/app/test.txt」がs3にアップロードされます。
$pathにはtemporaryUrlメソッドによって発行される一時的なアクセス用のURLが設定されます。今回はファイルの一般公開設定をしていないため、上記の一時アクセスURLによりファイルアクセスを行っています。

取得したパスを利用した画面表示は以下の記述で行います。

<img id="test_img" src="{{$path}}" alt="test_img" srcset="">

アップロードしたファイル(画像)をs3にアップロード後、画面に表示する

アップロード処理の実装

続いて、画面からファイルを選択しDBに情報を保存して利用する方法を考えていきます。ファイル本体はs3に保存してアクセスできるように実装をしていきます。

ファイルアップロードの場合、formタグに「enctype=”multipart/form-data」が必要になるので注意しましょう。
またMIMEタイプを利用して、画像のみアップロード可能なように設定しておくことで、想定外のファイルがアップロードされることを防止します。以下、コーディング例になります。

<form action="/upload" method="POST" enctype="multipart/form-data">
    @csrf
    <input type="file" name="example" accept="image/*">
    <button type="submit">Upload</button>
</form>

送信されたファイルをコントローラ側で受け取って、値をDBに格納していきます。
ファイルそのものをDBに保存するのではなく、s3にアクセスする際に必要なファイルパス部分をDBに格納していきます。
$reqはRequest、$entは保存するモデルクラスのインスタンスになっています。putFileAsメソッドでファイル名を指定して保存していますが、ファイル名の重複を避けるためにタイムスタンプを付与しています。

$file = $req->file('example');
$filename = time() . '_' . $file->getClientOriginalName();
$path = Storage::disk('s3')->putFileAs('uploads', $file, $filename);
$ent->path = "uploads/{$filename}";
$ent->save();

表示処理の実装

続いて表示処理を実装していきます。
モデルクラスにメソッドを作成して、s3のURLを返却するように実装します。
とりあえず下記のように実装してみました。temporaryUrlの引数に設定したいパスをDBに格納しているので、保存済みのパスを利用してtemporaryUrlでURLを発行しています。

public function getS3ReceiptTemporaryUrl(){
    return Storage::disk('s3')->temporaryUrl($this->expenses_receipt_path, now()->addMinutes(5));
}

発行されたURLを利用して様々な処理が実装できますね。別タブで画像を開いたり、imgタグで埋め込んだりと好きなように実装をすればs3との連携はOKです。

セキュリティ面も考え、s3の公開範囲を広げないようにしたり、IAMユーザーに必要以上の権限を持たせないようにしながら実装してみました。
もっと良い設定や実装があるかもしれませんが、出来る限りセキュリティ面も考えて実装してみたので、良かったら参考にしてください。