25
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS S3のアップロード用署名付きURLのPUTとPOSTのできることの違いについてまとめた

25
Last updated at Posted at 2025-12-16

本記事では、業務中調べるのに苦労した AWS の S3 のアップロード用署名付き URL の PUT と POST のできることの違いについてまとめました。
それぞれでできることが異なりますが、これをまとめている記事を見つけられなかったので備忘録がてら書いていきます。

S3 の署名付き URL とは

署名付き URL(Presigned URL)は、AWS の認証情報を持たないユーザーに対して、S3 バケットへの一時的なアクセス権を与える仕組みです。
URL や フォームデータ自体に認証情報が含まれているので、これらを知っていれば誰でもアクセスできるようになります。

署名付き URL には GETPUTPOST の 3 つの HTTP メソッドがあります。

メソッド 主な用途 AWS CLI で URL 発行可能か
GET S3 に保存されたファイルのダウンロード
PUT 1 つのファイルを直接 S3 にアップロード ×
POST フォームベースでのファイルアップロード ×

アップロード用署名付き URL を発行するための最小限コード

今回は TypeScript の SDK で紹介します。
S3のバケットポリシーは何も設定していないものとします。

PUT 最小限コード

長いURLが返ります。

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3Client = new S3Client({ region: "ap-northeast-1" });

const command = new PutObjectCommand({
  Bucket: "tora-ultra-bucket",
  Key: "test/sample.txt",
});

async function main() {
  const url = await getSignedUrl(s3Client, command);
  console.log(url);
}

main();
ログ例
https://tora-ultra-bucket.s3.ap-northeast-1.amazonaws.com/test/sample.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20251216%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20251216T083421Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJj...(省略)&X-Amz-Signature=dbe2741e98a06ac74b65cd56d79a5470...&X-Amz-SignedHeaders=host&x-amz-checksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject
curl例
$ curl -X PUT --upload-file ./sample.txt "https://tora-ultra-bucket.s3.ap-northeast-1.amazonaws.com/test/sample.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20251216%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20251216T083421Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJj...(省略)&X-Amz-Signature=dbe2741e98a06ac74b65cd56d79a5470...&X-Amz-SignedHeaders=host&x-amz-checksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject"

POST 最小限コード

URLとフォームデータが返ります。

import { S3Client } from "@aws-sdk/client-s3";
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";

const s3Client = new S3Client({ region: "ap-northeast-1" });

async function main() {
  const { url, fields } = await createPresignedPost(s3Client, {
    Bucket: "tora-ultra-bucket",
    Key: "test/sample.txt",
  });
  console.log("URL:", url);
  console.log("Fields:", fields);
}

main();
ログ例
URL: https://tora-ultra-bucket.s3.ap-northeast-1.amazonaws.com/
Fields: {
  'bucket': 'tora-ultra-bucket',
  'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
  'X-Amz-Credential': 'AKIAIOSFODNN7EXAMPLE/20251216/ap-northeast-1/s3/aws4_request',
  'X-Amz-Date': '20251216T084047Z',
  'X-Amz-Security-Token': 'IQoJb3JpZ2luX2VjEJj...(省略)',
  'key': 'test/sample.txt',
  'Policy': 'eyJleHBpcmF0aW9uIjoiMjAyNS0...(省略)',
  'X-Amz-Signature': '9c9bd3ef627b8df717dbcce67cad3783...'
}
curl例
$ curl -X POST "https://tora-ultra-bucket.s3.ap-northeast-1.amazonaws.com/" \
    -F "bucket=tora-ultra-bucket" \
    -F "X-Amz-Algorithm=AWS4-HMAC-SHA256" \
    -F "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20251216/ap-northeast-1/s3/aws4_request" \
    -F "X-Amz-Date=20251216T084047Z" \
    -F "X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJj..." \
    -F "key=test/sample.txt" \
    -F "Policy=eyJleHBpcmF0aW9uIjoiMjAyNS0..." \
    -F "X-Amz-Signature=9c9bd3ef627b8df717dbcce67cad3783..." \
    -F "file=@./sample.txt"

PUT と POST のどちらも Content-Type を指定しないとメタデータは Content-Type: binary/octet-stream となります。

PUT と POST のできることの違い早見表

URLを使う側ができること

先述の最小限コードで発行したURLを使ってファイルをアップロードしようとしたときにできることです。

項目 PUT POST
Content-Type の指定 ×
ファイル上書き
再利用
最大アップロードサイズ 5GB 明記なし
  • PUT で Content-Type を指定するには -H "Content-Type: ..." をつけるとできます
  • POST で -F "Content-Type=..." をつけると AccessDenied エラーになります
  • マルチパートアップロード機能を使用すると 48.8TiB までアップロードできます

署名付き URL を発行する側が制限できること

URLを発行する側が制限をかけたいとなると、PUTとPOSTでできることが異なります。

項目 PUT POST
Content-Type の制限
ファイルサイズ制限 ×
有効期限
上書き禁止 ×
キーをアップロード者に自由に設定させる ×

制限できることを詳しく

ここからコードとともに説明していきます。

Content-Type の制限

PUT ⭕️

できます

PutObjectCommandPutObjectCommandInputContentTypegetSignedUrl 関数 の第3引数に指定します。

const command = new PutObjectCommand({
  Bucket: "tora-ultra-bucket",
  Key: "test/sample.txt",
  ContentType: "text/plain", // 追加
});

async function main() {
  const url = await getSignedUrl(s3Client, command, {
    signableHeaders: new Set(["content-type"]), // 追加
  });
  console.log(
    `curl -X PUT -H "Content-Type: text/plain" --upload-file ./sample.txt "${url}"`
  );
}

もし Content-Type ヘッダーが無かったり、値が違ったりすると SignatureDoesNotMatch エラーが返ってきます。
また、URL を発行するときに signableHeaders を指定していないと、Content-Type が指定していないリクエストも受け付けてしまい、メタデータは Content-Type: binary/octet-stream となります。

POST ⭕️

できます。

Fields(と Conditions)に指定します。

const { url, fields } = await createPresignedPost(s3Client, {
  Bucket: "tora-ultra-bucket",
  Key: "test/sample.txt",
  Fields: {
    "Content-Type": "text/plain", // 追加
  },
  Conditions: [["eq", "$Content-Type", "text/plain"]], // 追加 なくてもよさそう
});

FieldsとConditionsについては以下の通りです

項目 説明
Fields 送信するフォームフィールドの初期値
Conditions リクエストを検証するためのポリシー条件

Fields に Content-Type が存在すると Conditions を書かずとも自動でポリシー設定されていたため、Conditions は書かなくてもいいかもしれません。
反対に Conditions だけを書いた場合、発行される URL のフォームデータには Content-Type が存在せず、そのまま使うとエラーになるというとんでも URL が生まれます。この時、リクエストに Content-Type を含めてあげると通ります。

ファイルサイズ制限

PUT ❌

先に書いた通り、最大ファイルサイズは 5GB に制限されており変更できません。
ただし、マルチパートアップロード機能を使用すると 48.8TiB までアップロードできます。(本記事では紹介しません)

POST ⭕️

できます。

Conditions に content-length-range を設定します。

const { url, fields } = await createPresignedPost(s3Client, {
  Bucket: "tora-ultra-bucket",
  Key: "test/sample.txt",
  Conditions: [
    ["content-length-range", 0, 1024 * 1024], // 追加 この場合は最大1MB
  ],
});

設定できる範囲は明記されていませんでした。

有効期限

PUT ⭕️

できます

getSignedUrl 関数 の第3引数に指定します。

const url = await getSignedUrl(s3Client, command, {
  expiresIn: 60, // 追加 60秒
});

そもそも設定していないとデフォルトは 15 分になります。

PutObjectCommandPutObjectCommandInput に指定できる Expires は別物なので注意です。

POST ⭕️

できます

Expires に数値を設定します。

const { url, fields } = await createPresignedPost(s3Client, {
  Bucket: "tora-ultra-bucket",
  Key: "test/sample.txt",
  Expires: 60, // 追加 60秒
});

そもそも設定していないときのデフォルトの有効期限が何分になるのかの記述は見つけられませんでした。

上書き禁止

PUT ⭕️

できます

PutObjectCommandPutObjectCommandInputIfNoneMatch(と getSignedUrl 関数 の第3引数)に指定します。

const command = new PutObjectCommand({
  Bucket: "tora-ultra-bucket",
  Key: "test/sample.txt",
  IfNoneMatch: "*", // 追加
});

async function main() {
  const url = await getSignedUrl(s3Client, command, {
    signableHeaders: new Set(["if-none-match"]), // 追加 なくてもよさそう
  });
  console.log(
    `curl -X PUT -H "If-None-Match: *" --upload-file ./sample.txt "${url}"`
  );
}

すでに同じ Key で存在している場合は 412 PreconditionFailed エラーが返ります。
signableHeaders を指定しなくても If-None-Match ヘッダーがないリクエストはエラーになったため、signableHeaders は書かなくてもいいかもしれません。

POST ❌

AI に聞くと「できるよ!」って元気に返してくることがありますができません

キーをアップロード者に自由に設定させる

PUT ❌

固定のキーでしか発行できません。

POST ⭕️

できます

キーに ${filename} を設定することでできます。
この例の場合は uploads/ 以降をアップロードする側に自由に決めてもらえます。

const { url, fields } = await createPresignedPost(s3Client, {
  Bucket: "tora-ultra-bucket",
  Key: "uploads/${filename}", // 編集
  Conditions: [["starts-with", "$key", "uploads/"]], // 追加 なくてもよさそう
});

Conditions を書かずとも自動でポリシー設定されていたので書かなくてもよさそうです。

以下のようなとんでも URL を発行できてしまうので設定の仕方には注意してください。

const { url, fields } = await createPresignedPost(s3Client, {
  Bucket: "tora-ultra-bucket",
  Key: "${filename}",
});

おわりに

ここで紹介した以外のことでも POST の方が多くのことができますが、PUT でないとできないこともいくつかあります。
AI は嘘つきがちなので実現可能か調査して POST を使うか PUT を使うか選択しましょう!

参考サイト

25
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?