← 學習筆記
2026-05-23 AWS · S3

AWS S3 Presigned URL

不開放 S3 bucket 公開讀取,仍能讓外部用戶暫時存取特定物件的機制。

背景

參與公司「雲世代」計畫,在 AWS 上實作了一個協助行銷部門加速 EDM 產製的 GenAI 專題。 其中有個功能是讓使用者上傳圖檔,一開始以為寫一支上傳 API 就搞定了,沒想到卡在 S3 的權限設定。

第一個念頭是直接把 Bucket 設成 public —— 簡單,但這等於讓任何人都能存取裡頭的所有檔案,有明顯的資料外洩風險。 改用 presigned URL 之後,由後端產生一組有時效、限定目標物件的網址,使用者在不開放整個 Bucket 的情況下也能安全上傳。


先備知識

閱讀這篇前,建議先對以下 AWS 概念有基本認識:

  • S3(Simple Storage Service) — AWS 的物件儲存服務。檔案以「Bucket / Key」結構存放,Bucket 是頂層容器,Key 是物件的路徑名稱。
  • Bucket 存取控制 — S3 Bucket 預設為 private,需要明確授權才能存取其中的物件。
  • IAM(Identity and Access Management) — AWS 的身份與權限管理服務。每個操作都必須由具備對應權限的 IAM 身份(User 或 Role)來執行。

是什麼

Presigned URL 是一條帶有簽名的 S3 URL,讓沒有 AWS 憑證的人也能在有效期限內對特定物件執行指定操作(下載或上傳)。 簽名是由具備權限的 IAM 身份(通常是後端服務)在伺服器端產生的,有效期過後 URL 自動失效。

重點:Presigned URL 的權限來自「產生它的 IAM 身份」,而非呼叫者本身。 如果 IAM 身份的 S3 權限被撤銷,該 URL 立即失效,即使還在有效期內。

運作原理

產生時,AWS SDK 會把以下資訊打包進 URL 的 query string:

  • X-Amz-Credential — IAM 身份與 region
  • X-Amz-Expires — 有效秒數
  • X-Amz-Date — 產生時間(ISO 8601)
  • X-Amz-Signature — HMAC-SHA256 簽名

S3 收到請求時,重新計算簽名比對,並確認是否在有效期內,兩者都通過才執行操作。


兩種用法

GET — 暫時允許下載

最常見的場景:S3 bucket 設為 private,但需要讓用戶或前端短暫存取某個檔案。

import boto3

s3 = boto3.client('s3', region_name='ap-northeast-1')

url = s3.generate_presigned_url(
    ClientMethod='get_object',
    Params={
        'Bucket': 'my-bucket',
        'Key':    'reports/2026-05.pdf',
    },
    ExpiresIn=3600,  # 秒,最長 7 天(若用 STS 臨時憑證則最長受 session 限制)
)

# url 是一條帶簽名的 HTTPS URL,直接放到 <a href> 或 fetch() 都可以用

PUT — 讓前端直接上傳到 S3

不想讓檔案先經過你的伺服器再轉到 S3(流量費 + 延遲),可以讓前端拿到 PUT presigned URL 後直接上傳。 後端只負責產生 URL,S3 直接收到檔案。

url = s3.generate_presigned_url(
    ClientMethod='put_object',
    Params={
        'Bucket':      'my-bucket',
        'Key':         f'uploads/{user_id}/{filename}',
        'ContentType': 'image/jpeg',  # 要和前端上傳時的 Content-Type 一致
    },
    ExpiresIn=300,  # 上傳用途通常設短一點
)

# 前端用 PUT 把檔案送到這條 URL
# fetch(url, { method: 'PUT', body: file, headers: { 'Content-Type': 'image/jpeg' } })
注意:PUT presigned URL 產生時指定了 ContentType,前端上傳時的 Content-Type header 必須完全一致,否則 S3 會回 403。

安全注意事項

  • 有效期設越短越好 — 下載用 1 小時內,上傳用 5–15 分鐘通常已足夠。URL 一旦外洩,任何人都能在期限內使用。
  • Key 不要可猜測 — 上傳路徑建議包含 UUID 或 user_id,避免用序號、時間戳等可推測的格式。
  • 不要在前端產生 — 產生 URL 需要 AWS 憑證,永遠只能在後端做。
  • IAM 最小權限 — 產生 URL 的 IAM role 只需要 s3:GetObject(下載)或 s3:PutObject(上傳)on 特定 bucket/prefix,不需要 s3:*
  • CORS 設定 — 前端直接 PUT 到 S3 時,bucket 需要設 CORS,允許對應 origin 的 PUT method。

常見坑

  • 用 STS 臨時憑證產生的 Presigned URL,有效期不能超過該 STS session 的剩餘時間(即使 ExpiresIn 設更長也無效)。
  • 如果 IAM 政策走 VPC endpoint,URL 可能只在 VPC 內有效,外部存取會收到 403。
  • Bucket 如果開了 require MFA delete 或 Object Lock,Presigned URL 無法用來刪除物件。