- バックエンド
- PdM
- 急成長中の福利厚生SaaS
- Other occupations (24)
- Development
- Business
- Other
AWS S3 署名付きアップロード機能 Presigned URLとPOST Policyの比較
こんにちは、ウォンテッドリーでバックエンドエンジニアをしている平岡です 。
サーバーを経由せずクライアントからS3へ直接ファイルをアップロードする技術は、より良いユーザ体験を作るための選択肢として昨今のWebアプリケーション開発では欠かせないものとなっています。
本記事では、それらを実現する技術として、AWS S3のPresigned URLとPOST Policyについて調べたことをまとめてみたいと思います。
目次
Presigned URL
POST Policy
CORS制約(Cross-Origin Resource Sharing)
まとめ
Presigned URL
Presigned URLは、S3オブジェクトに対する一時的なアクセス権限付きのURLを発行する仕組みです。
- サーバー側の役割:
AWS SDK(例:Pythonのboto3やNode.jsのAWS SDK)を使用し、有効期限付きのPUTリクエスト用URLを生成します。
# サーバーサイド(Python / Boto3 での例)
# 有効期限(ExpiresIn)とアップロード先のキーを指定
url = s3_client.generate_presigned_url(
ClientMethod='put_object',
Params={'Bucket': 'your-bucket-name', 'Key': 'path/to/file.txt'},
ExpiresIn=3600 # 1時間有効
)
# クライアントへこのURLを返す- クライアント側の役割:
サーバーから受け取ったURLに対して、ファイルを直接PUTリクエストで送信します。認証情報はURLに含まれているため、クライアント側で別途認証ヘッダーを用意する必要はありません。
// クライアントサイド(JavaScript / Fetch API の概念例)
const file = document.getElementById('file-input').files[0];
fetch(presignedUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
})
// ... 進捗管理やエラーハンドリングPOST Policy
POST Policyは、アップロードを許可する条件(Policy Document)と署名を生成し、クライアントに渡します。クライアントはこの情報を使ってS3のエンドポイントへHTMLフォームを送信します。
- サーバー側の役割:
アップロードのファイルサイズ、ファイル名(プレフィックス)、有効期限などの条件を定義したポリシーを生成し、そのポリシーに対して署名を行います。
require 'openssl'
require 'time'
# 1. 設定情報
AWS_ACCESS_KEY_ID = 'AKIAIOSFODNN7EXAMPLE' # 実際のものに置き換えてください
AWS_SECRET_ACCESS_KEY = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' # 実際のものに置き換えてください
AWS_REGION = 'ap-northeast-1'
BUCKET_NAME = 'your-bucket-name'
KEY_PREFIX = 'uploads/'
EXPIRATION_TIME = (Time.now.utc + 3600).iso8601.gsub(/\.\d+Z/, 'Z') # 例: 1時間後
# 2. ポリシードキュメントの生成
policy_document = {
'expiration' => EXPIRATION_TIME,
'conditions' => [
{'bucket' => BUCKET_NAME},
['starts-with', '$key', KEY_PREFIX],
{'acl' => 'public-read'}, # 例としてpublic-read ACLを含める
{'success_action_status' => '201'}, # アップロード成功時のHTTPステータス
{'x-amz-credential' => "#{AWS_ACCESS_KEY_ID}/#{Date.today.strftime('%Y%m%d')}/#{AWS_REGION}/s3/aws4_request"},
{'x-amz-algorithm' => 'AWS4-HMAC-SHA256'},
{'x-amz-date' => Time.now.utc.strftime('%Y%m%dT%H%M%SZ')}
]
}
# 3. Base64ポリシー文字列の生成 (署名の対象となる文字列)
policy_json = policy_document.to_json
base64_policy = Base64.strict_encode64(policy_json)
puts "Base64 Policy: #{base64_policy}"
# 4. 署名鍵の生成 (SigV4 Key Derivation)
# 署名バージョン4 (SigV4) の鍵導出プロセスに従います
def get_signature_key(key, date_stamp, region_name, service_name)
k_date = OpenSSL::HMAC.digest('sha256', "AWS4#{key}", date_stamp)
k_region = OpenSSL::HMAC.digest('sha256', k_date, region_name)
k_service = OpenSSL::HMAC.digest('sha256', k_region, service_name)
k_signing = OpenSSL::HMAC.digest('sha256', k_service, 'aws4_request')
k_signing
end
date_stamp = Date.today.strftime('%Y%m%d')
signature_key = get_signature_key(AWS_SECRET_ACCESS_KEY, date_stamp, AWS_REGION, 's3')
# 5. 署名の生成 (Base64ポリシーに対するHMAC-SHA256)
# 署名は、導出した署名鍵とBase64ポリシー文字列を使用して計算されます。
signature = OpenSSL::HMAC.hexdigest('sha256', signature_key, base64_policy)
puts "---"
puts "AWS Access Key ID: #{AWS_ACCESS_KEY_ID}"
puts "x-amz-credential: #{AWS_ACCESS_KEY_ID}/#{date_stamp}/#{AWS_REGION}/s3/aws4_request"
puts "x-amz-date: #{Time.now.utc.strftime('%Y%m%dT%H%M%SZ')}"
puts "Policy (Base64): #{base64_policy}"
puts "**Signature (x-amz-signature):** #{signature}"- クライアント側の役割:
S3のエンドポイントをactionに指定したHTMLフォームを作成し、サーバーから受け取ったポリシー情報や署名をhiddenフィールドとして埋め込みます。ユーザーがファイルを選択しフォームを送信すると、S3に直接POSTリクエストが送られます。
<form action="https://<%= BUCKET_NAME %>.s3.amazonaws.com/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="key" value="<%= KEY_PREFIX %>${filename}" />
<input type="hidden" name="policy" value="<%= base64_policy %>" />
<input type="hidden" name="x-amz-signature" value="<%= signature %>" />
<input type="hidden" name="x-amz-credential" value="<%= AWS_ACCESS_KEY_ID %>/<%= date_stamp %>/<%= AWS_REGION %>/s3/aws4_request" />
<input type="hidden" name="x-amz-algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="x-amz-date" value="<%= Time.now.utc.strftime('%Y%m%dT%H%M%SZ') %>" />
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="success_action_status" value="201" />
<input type="file" name="file" />
<input type="submit" value="Upload to S3" />
</form>CORS制約(Cross-Origin Resource Sharing)
Presigned URLは、前述の通りJavaScriptのfetchやXMLHttpRequestといったスクリプト通信(PUTリクエスト)としてS3にアクセスする仕組みです。JavaScriptでS3(異なるオリジン)にアクセスするため、CORS(Cross-Origin Resource Sharing)の設定が必要になります。
一方POST Policy に関しては、基本的に純粋なHTMLフォーム送信として使う限りは、CORSの設定を行う必要はありません。
まとめ
いかがでしたでしょうか?同じファイルアップロードを実現する技術でありながら、Presigned URLはJavaScriptでのアクセスを前提にリクエストする仕組みであり、一方Post Policyは、HTMLのformを前提にリクエストする仕組みという違いがあります。
それゆえ、セキュリティ上の制約にも違いがありその違いを理解した上で使い分けをすることが必要になります。