環境
- PHP 7.4.1
- Composer 1.9.3
- Laravel Framework 6.14.0
- league/flysystem-aws-s3-v3 1.0.24
- aws/aws-sdk-php 3.133.40
- aws/aws-sdk-php-laravel 3.5.0
手順
長いのでだいぶ雑です。
1. S3アップロード実装
まず、AWSアカウントの作成, S3バケットの作成, IAMポリシーの作成, IAMユーザの作成/IAMポリシー適用などが必要になります。
これらは、ググれば良い資料が沢山出てくるのでそちらを参照してください。
S3のバケット作成後、バケットポリシーには、IAMユーザからのアクションを許可する設定を書いておいてください。
ポリシージェネレーターを使うか、以下の例を書き換えて使用するのがオススメです。
本番環境がEC2の場合は、セキュリティリスクの観点から、IAMユーザではなくIAMロールの利用を検討してみてください。
バケットポリシー例(全アクションを許可)
{
"Id": "Policy1584851110472",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1581137583020",
"Action": "s3:*",
"Effect": "Allow",
"Resource": "arn:aws:s3:::example-bucket",
"Principal": {
"AWS": [
"arn:aws:iam::000000000000:user/hoge_user"
]
}
}
]
}
公式ドキュメントに則り、Flysystemを使ってS3に画像をアップロードするところまで進めます。
composer require league/flysystem-aws-s3-v3
config/filesystems.php
には既にS3用の記述が存在するため、編集する必要はありません。
.env
ファイルは自身の環境に合わせて書き換えましょう。
'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'),
],
AWS_ACCESS_KEY_ID=hoge
AWS_SECRET_ACCESS_KEY=fuga
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=hoga
AWS_URL=https://example-bucket.s3-ap-northeast-1.amazonaws.com
画像アップロード用のHTMLとコントローラ、ルーティングを作成します。
Route::get('/', function () {
return view('index');
});
Route::Post('/', 'TestController@create');
<form method="POST" action="/" enctype="multipart/form-data">
{{ csrf_field() }}
<input type="file" name="media" accept="image/*">
<input type="submit">
</form>
use Illuminate\Http\Request;
use Storage;
public function create(Request $request) {
// S3アップロード
$media = $request->file('media');
$path = Storage::disk('s3')->putFile('/', $media);
$url = Storage::disk('s3')->url($path);
dd($url);
}
アップロードがうまくいかない場合、IAMポリシーやアクセスキー、バケットポリシーのチェックを行い、php artisan cache:clear
やphp artisan config:clear
を叩いてみましょう。
この状態では、URLを叩くとエラーになるはずです。
画像をブラウザで確認する場合、S3バケットのパブリックアクセスをオンにした上でオブジェクトを公開状態にする必要があります。
S3バケットの「パブリックアクセスをすべてブロック」をオフにして、putFile()の第三引数に'public'を追記
してアップロードすると、見れるようになるはずです。
今回のゴールは、オブジェクトを公開せずに見れるようにすることです。確認が終わったらパブリックアクセスをオフに戻しておきましょう。
2. CloudFrontとの連携
ルートユーザでAWSマネジメントコンソールにアクセスし、「マイセキュリティ資格情報」からCloudFrontキーペアを生成します。
キーペアをダウンロードし、秘密鍵をアプリケーションディレクトリ直下に保存します。
-----BEGIN RSA PRIVATE KEY-----
aaaaaaaaaaaaa
bbbbbbbbbbbbb
ccccccccccccc
...
-----END RSA PRIVATE KEY-----
次に、CloudFrontのページで「Create Distribution」→Webの「Get Started」と進み、以下の情報を入力して設定を完了します。StatusがDeployedに変わるまで待ちましょう。
この操作が完了すると、S3のバケットポリシーが更新され、CloudFront経由でしかアクセスできないようになります。
Origin Domain Name: S3のバケットを選択
Restrict Bucket Access: Yes
Origin Access Identity: Create a New Identity
Grant Read Permissions on Bucket: Yes, Update Bucket Policy
Restrict Viewer Access(Use Signed URLs or Signed Cookies): Yes
Trusted Signers: Self
次に、以下のコマンドでAWS SDKを導入します。
コマンド入力後は、 https://github.com/aws/aws-sdk-php-laravel に記載のある通り、providersとaliasesを更新します。
composer require aws/aws-sdk-php-laravel
ここまでできたら、後は署名付きURLを取得するだけです。
コントローラと.envファイルを以下のように書き換えます。
use Illuminate\Http\Request;
use Storage;
use AWS;
public function create(Request $request) {
// S3アップロード
$media = $request->file('media');
$path = Storage::disk('s3')->putFile('/', $media);
$url = Storage::disk('s3')->url($path);
// 署名付きURLを取得
$client = AWS::createClient('cloudfront');
$signedUrl = $client->getSignedUrl([
'url' => $url,
'expires' => time() + 30, // 30秒間有効
'private_key' => base_path('pk-hogefuga.pem'),
'key_pair_id' => env('AWS_CLOUDFRONT_KEY_PAIR_ID')
]);
dd($signedUrl);
}
AWS_URL=https://xxx.cloudfront.net # CloudFrontのページにある「Domain Name」
AWS_CLOUDFRONT_KEY_PAIR_ID=hogefuga # 鍵ファイル名のハイフン以降の文字列
実行後、以下のようなURLが取得できたらうまくいっています。
CloudFront経由でしかアクセスできない(S3に直接アクセスが禁止されている)こと、GETパラメータ(署名)を外すとアクセスできないこと、有効期限を過ぎるとアクセスできないことを確認してみてください。
余談
以下のコードでもS3にアップロードできます。
個人的にはこちらのほうが直感的で好きです。
$path = $request->file('media')->store('/', 's3');