在 Web 或 App 系統中,常見使用者需要上傳檔案(例如:大頭照、報表、附件)。若直接開放 S3 Public Write,會導致嚴重安全風險;若透過後端 Proxy 上傳,則增加伺服器壓力與流量成本。
Presigned URL 提供一種安全、輕量的方式,讓使用者在短時間內直接與 S3 互動,並避免憑證外洩與濫用。
(1) 痛點在於如何允許使用者安全上傳檔案,而不暴露 S3 的寫入權限。
(2) 傳統做法若讓 API Gateway / Lambda 代理檔案上傳,會大幅增加網路流量與成本,且成為瓶頸。
(3) Presigned URL 則允許客戶端在特定時間內直接 PUT 檔案至 S3,結合 IAM 權限控管與條件限制(檔案大小、檔名格式),達到最佳平衡。
在 Serverless 架構中,這個模式通常搭配 Cognito 進行授權,Lambda 產生 Presigned URL,前端再使用該 URL 完成檔案上傳。
(1) Amazon S3:存放上傳檔案,設定 Bucket Policy 控制存取權限。
(2) AWS Lambda:產生 Presigned URL,並回傳給前端。
(3) Amazon API Gateway:提供安全的 API 入口,讓前端取得 Presigned URL。
(4) IAM Policy:控制 Lambda 產生的 Presigned URL 僅能用於指定 Bucket 與動作(s3:PutObject
)。
(5) Amazon CloudWatch Logs:監控 Lambda 呼叫與上傳行為。
(1) 限制 Presigned URL 有效時間(通常 5~15 分鐘)。
(2) 搭配 檔案大小限制與 檔案副檔名檢查,避免惡意檔案上傳。
(3) 若應用需要安全審計,可搭配 S3 Event + Lambda 進行後續處理(如掃毒、壓縮)。
(4) 搭配 Cognito 管控使用者身分,避免所有人都能產生 URL。
(5) S3 Bucket 必須開啟 Block Public Access,避免繞過 Presigned URL。
💡在 GitHub 網站上,你無法直接創建一個空的資料夾。這是因為 Git(GitHub 背後的版本控制系統)的設計理念是只追蹤檔案,不追蹤資料夾。
要讓 Git 和 GitHub 追蹤你的資料夾,你必須在裡面放一個**「佔位」檔案**。這個檔案通常是 .gitkeep
或一個簡單的 .gitignore
檔案。
到自己的GitHub中,並點選創建檔案。
創建資料夾及佔位檔案「.gitkeep」。
上傳並推送。
進入「Lambda」頁面。
創建一個新的函數。
輸入函數名稱,並選擇編撰語言。
跳過建議畫面。
寫入程式碼,並部署。
程式碼範例
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3Client = new S3Client({ region: process.env.AWS_REGION });
const BUCKET_NAME = process.env.BUCKET_NAME; // 從環境變數讀取S3 bucket name
const FOLDER_PATH = process.env.FOLDER_PATH; // 從環境變數讀取資料夾路徑
const EXPIRES_IN = parseInt(process.env.EXPIRES_IN, 10) || 300; // 從環境變數讀取過期時效
export const handler = async (event, context) => {
if (!event.queryStringParameters || !event.queryStringParameters.filename) {
return {
statusCode: 400,
body: JSON.stringify("Missing required parameter 'filename'"),
};
}
// 修改這裡:將資料夾路徑加到檔名前面
const key = FOLDER_PATH + event.queryStringParameters.filename;
const command = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key, // 現在 Key 的值會是 'ducky-test/test.jpg'
});
try {
const presignedUrl = await getSignedUrl(s3Client, command, {
expiresIn: EXPIRES_IN,
});
return {
statusCode: 200,
body: JSON.stringify(presignedUrl),
};
} catch (err) {
console.error(err);
return {
statusCode: 500,
body: JSON.stringify("Failed to generate presigned URL."),
};
}
};
進入「組態」分頁,設定環境變數。
設定以下的Key及Value。
BUCKET_NAME:S3 bucket name
FOLDER_PATH:存檔路徑
EXPIRES_IN:有效時限(秒)
完成畫面。
進入「IAM 」頁面。
進入IAM role的頁面,點選該Lambda自動創建的IAM role。
新增「許可政策」。
增加S3的「PutObject」權限。
將授權調整為指定的S3。
- S3的ARN在哪?
設定完成後,點選下一步。
設定「許可政策」名稱。
完成畫面。
進入「API Gateway」服務頁面。
進入Day12創建的HTTP API Gateway。(主架構)
創建一個新的路由路徑。
設定HTTP Method為「GET」,並自定義API路徑。(此次路徑設定為「/get-upload-url」)
將路由整合新的應用。
關聯剛剛創建的Lambda函數。
完成關聯。
指令範例
curl "<YOUR_API_GATEWAY_URL>/get-upload-url?filename=<上傳後的檔案名稱>"
指令範例
curl --upload-file <本地檔案路徑.格式> "<YOUR_PRESIGNED_URL>"
本 Lab 展示如何透過 S3 Presigned URL 提供安全、限時的檔案上傳機制。
它解決了傳統架構中 Public Access 的安全問題,以及 Proxy 上傳的效能與成本問題。這種模式能大幅減少 API Gateway 與 Lambda 的流量負擔,並確保資料存取安全性,是 Serverless 架構中檔案上傳的最佳實踐之一。