generate_presigned_urlとは?
boto3のgenerate_presigned_urlメソッドは、S3クライアントを使って、特定のS3操作(例:get_objectやput_object)のための署名付きURLを生成します。このURLにはAWSの認証情報に基づく署名が含まれており、指定した有効期限内でのみアクセスが可能です。主な用途は、プライベートバケットのファイルを一時的に共有したり、クライアントが直接S3にアップロードできるようにすること。
主な特徴
- 一時的アクセス: 有効期限(秒単位で指定)を設定可能。期限後はURLが無効に。
- セキュリティ: AWSのSignature Version 4で署名を生成し、改ざん防止。
- 柔軟性: ダウンロード(GET)、アップロード(PUT)、削除(DELETE)など、さまざまなS3操作に対応。
- 簡単さ: 数行のコードでURLを生成可能。
基本的な使い方
generate_presigned_urlの基本構文は以下の通りです
import boto3
s3_client = boto3.client('s3')
url = s3_client.generate_presigned_url(
ClientMethod='method_name', # 実行するS3操作(例:get_object, put_object)
Params={'Bucket': 'bucket_name', 'Key': 'object_key'}, # S3操作のパラメータ
ExpiresIn=3600 # 有効期限(秒)
)
パラメータの詳細
-
ClientMethod(必須):- S3の操作を指定。主な値:
-
'get_object': ファイルのダウンロード。 -
'put_object': ファイルのアップロード。 -
'delete_object': ファイルの削除。
-
- 他にも
head_object(メタデータ取得)など、S3のAPIに対応したメソッドを指定可能。
- S3の操作を指定。主な値:
-
Params(必須):- S3操作に必要なパラメータを辞書形式で指定。
- 必須キー:
-
Bucket: 対象のS3バケット名。 -
Key: 対象のオブジェクトキー(ファイルのパス)。
-
- 任意キー(例):
-
ContentType: アップロード時のファイル形式(例:'image/jpeg')。 -
Metadata: アップロード時に付与するメタデータ。 -
ResponseContentDisposition: ダウンロード時のファイル名指定(例:'attachment; filename="download.pdf"')。
-
-
ExpiresIn(必須):- URLの有効期限(秒単位)。最大は7日(604800秒)だが、セキュリティのため短めに設定推奨(例:数分~数時間)。
-
HttpMethod(任意):- HTTPメソッドを明示的に指定(例:
'GET'、'PUT')。通常はClientMethodから自動推論されるので省略可。
- HTTPメソッドを明示的に指定(例:
具体例と実装
プロジェクトで実際に使った例を基に、ダウンロードとアップロードのケースを解説します。プロジェクトでは、S3のプライベートバケットにあるPDFや画像をチームでプレビューするためのビューワーを作っていて、署名付きURLで安全にアクセスを実現した。
1. ダウンロード用URL(get_object)
チームがS3のレポートPDFをプレビューできるように、get_object用の署名付きURLを生成。
import boto3
from botocore.exceptions import ClientError
# ローカル開発用にプロファイルを指定
session = boto3.Session(profile_name='my-profile')
s3_client = session.client('s3')
try:
url = s3_client.generate_presigned_url(
ClientMethod='get_object',
Params={
'Bucket': 'my-project-bucket',
'Key': 'reports/client-report.pdf',
'ResponseContentDisposition': 'inline' # ブラウザで直接表示
},
ExpiresIn=3600 # 1時間有効
)
print("ダウンロードURL:", url)
except ClientError as e:
print(f"エラー: {e}")
ポイント
-
ResponseContentDisposition:'inline'を指定すると、PDFがブラウザで直接開く。'attachment; filename="report.pdf"'にすればダウンロードになる。 - 有効期限: プレビュー用途なので1時間に設定。短いほうがセキュリティ的に安心。
-
エラーハンドリング:
ClientErrorでIAM権限エラーやバケットが存在しない場合をキャッチ。
2. アップロード用URL(put_object)
ユーザーがウェブアプリから画像を直接S3にアップロードできるように、put_object用の署名付きURLを生成。
import boto3
from botocore.exceptions import ClientError
session = boto3.Session(profile_name='my-profile')
s3_client = session.client('s3')
try:
url = s3_client.generate_presigned_url(
ClientMethod='put_object',
Params={
'Bucket': 'my-project-bucket',
'Key': f'uploads/user-{user_id}/image.jpg',
'ContentType': 'image/jpeg',
'Metadata': {'uploaded-by': user_id}
},
ExpiresIn=1800 # 30分有効
)
print("アップロードURL:", url)
except ClientError as e:
print(f"エラー: {e}")
クライアント側でのアップロード例(TypeScript)
フロントエンドでこのURLを使ってアップロード:
import axios from 'axios';
const uploadFile = async (file: File, presignedUrl: string) => {
try {
const response = await axios.put(presignedUrl, file, {
headers: { 'Content-Type': 'image/jpeg' }
});
console.log('アップロード成功:', response.status);
} catch (error) {
console.error('アップロード失敗:', error);
}
};
ポイント
-
ContentType: アップロードするファイルのMIMEタイプを指定。クライアント側も同じContent-Typeヘッダーを設定しないと、S3が正しく認識しない。 -
Metadata: アップロード時にカスタムメタデータを付与可能。後で誰がアップロードしたか追跡するのに便利。 - 有効期限: アップロードは短時間で済むので30分に設定。
3. その他の操作(例:削除)
削除用の署名付きURLも生成可能。例:
url = s3_client.generate_presigned_url(
ClientMethod='delete_object',
Params={'Bucket': 'my-project-bucket', 'Key': 'reports/old-file.pdf'},
ExpiresIn=600 # 10分有効
)
クライアント側でDELETEリクエストを送れば削除可能。ただし、削除は慎重に(間違って消すと復元できない)。
ハマったポイントと対策
実装中にハマったことをメモ。
-
クレデンシャルエラー
ローカル環境でNoCredentialsErrorが出て詰まった。AWS CLIでaws configure --profile my-profileを設定し、コードでboto3.Session(profile_name='my-profile')を指定することで解決。デプロイ環境ではEC2やECSのIAMロールを使ったので、この問題はローカル限定だった。 -
IAM権限
s3:GetObjectやs3:PutObjectの権限がなくて「Access Denied」。IAMポリシーを確認し、以下を追加:{ "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"], "Resource": ["arn:aws:s3:::my-project-bucket", "arn:aws:s3:::my-project-bucket/*"] } -
Content-Typeのミスマッチ
アップロード時にContentTypeを指定しないと、S3がデフォルトでapplication/octet-streamに設定。クライアント側と一致させるため、ParamsにContentTypeを明示。 -
有効期限の設定
最初、1日(86400秒)にしたら長すぎるとチームから指摘。プレビューなら5~10分、アップロードなら30分くらいで十分。用途に応じて調整した。 -
ファイルキーのエンコーディング
ファイルキーにスラッシュや特殊文字がある場合、URLエンコーディングに注意。FastAPIの{key:path}が上手く処理してくれたが、特殊文字が多い場合はurllib.parse.quoteを検討。
ベストプラクティス
- 有効期限を短く: セキュリティのため、必要最低限の時間(例:5分~1時間)に設定。
-
クレデンシャル管理: ローカルでは
profile_name、本番ではIAMロールを使う。アクセスキー直書きは絶対NG。 -
エラーハンドリング:
ClientErrorをキャッチして、ユーザー向けに分かりやすいエラーメッセージを返す。 - ログ: 生成したURLやエラーをログに残すと、デバッグが楽。
- Content-Type: アップロード時はクライアントとサーバーで一致させる。MIMEタイプのリストを事前に決めておくと安心。