iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Build on AWS

從一個網站的誕生,看懂 AWS 架構與自動化的全流程!系列 第 20

Day 20 檔案分享功能:限時連結 x 權限管理 API 設計

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20251004/20172743io28cOGTpU.png

一、前言

檔案分享是會員系統的核心需求之一,但如果直接公開 S3 檔案連結,將導致任何人都能存取,存在嚴重的安全風險。因此我們需要設計一套「限時連結」與「權限檢查」機制,確保只有符合資格的用戶,且僅在設定的有效時間內,才能下載或預覽檔案。

這個 Lab 解決了兩大痛點:
(1) 如何在伺服器端驗證使用者身分,並只針對有權限的人產生下載連結。
(2) 如何確保連結具備「限時」屬性,避免外流或長期可用。

在整體 Serverless 架構中,它扮演 檔案分享的安全閘道,結合 Cognito(身分驗證)、API Gateway(流量管理)、Lambda(邏輯處理)與 S3(檔案儲存),打造符合企業等級安全的分享機制。

二、需要使用到的服務

  1. Amazon S3:檔案儲存來源,提供下載目標。
  2. AWS Lambda:負責檢查會員權限,並產生 Presigned URL。
  3. Amazon API Gateway:作為安全 API 出入口,提供「請求下載連結」的管道。
  4. Amazon Cognito:驗證會員身份,確保 API 只能被合法會員呼叫。
  5. DynamoDB(可選):紀錄分享紀錄與權限清單,例如「哪些會員可下載哪些檔案」。

三、架構/概念圖

https://ithelp.ithome.com.tw/upload/images/20251004/20172743B0ueThkR7m.png

四、技術重點

  1. 限時連結過期時間控制:建議設定在 5~15 分鐘,不建議過長。
  2. 權限檢查前置化:下載連結應在 Lambda 層級驗證權限,避免所有檔案都能生成 URL。
  3. 審計紀錄:使用 DynamoDB 或 CloudWatch Logs 記錄分享紀錄,方便後續稽核。
  4. 細粒度權限:透過 IAM Policy 或 DynamoDB 控制,不同群組會員只能存取特定檔案。

五、Lab流程

1️⃣ 前置作業

1. 準備一個 S3 bucket 作為檔案儲存區。(Day19)

2. 建立一個 Cognito User Pool,確保使用者能先登入獲取 Token。(Day13)

2️⃣ 主要配置

1. 建立 Lambda 函數(設定限制限制

  1. 進入「Lambda」服務頁面。
    https://ithelp.ithome.com.tw/upload/images/20251004/201727430MA95Ihdtq.png

  2. 點選「創建函數」。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743PcExzj3DAd.png

  3. 輸入函數名稱、選用指定的代碼編輯器、執行角色,並按下建立。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743DKjAqBi8ab.png

  4. 退出推薦的模板。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743appPbCg8IS.png

  5. 撰寫Lambda內容+部署。

    • 程式碼範例

      // index.mjs (generateDownloadLink)
      import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
      import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
      
      const s3Client = new S3Client({ region: process.env.AWS_REGION });
      
      // 🎯 從環境變數讀取 S3 儲存桶名稱
      const BUCKET_NAME = process.env.DOWNLOAD_BUCKET; 
      
      // 🎯 新增環境變數:用於指定檔案儲存的根路徑 (例如 'member-files/')
      const FILE_PREFIX_PATH = process.env.FILE_PREFIX_PATH || ""; 
      
      if (!BUCKET_NAME) {
          throw new Error('Environment variable DOWNLOAD_BUCKET must be set.');
      }
      
      export const handler = async (event) => {
          // 檢查是否缺少 'file' 查詢參數
          if (!event.queryStringParameters?.file) {
              return {
                  statusCode: 400,
                  body: JSON.stringify({ message: "Missing file parameter." }),
              };
          }
      
          // 關鍵修正:將檔案路徑字首與使用者傳入的檔名結合
          // 確保只存取到 FILE_PREFIX_PATH 內的檔案
          const fileKey = FILE_PREFIX_PATH + event.queryStringParameters.file;
      
          // 🎯 技術重點:這裡應加入權限檢查邏輯 (保持不變)
      
          // 產生 GetObject 的指令
          const command = new GetObjectCommand({
              Bucket: BUCKET_NAME, // 使用環境變數的值
              Key: fileKey,        // 使用結合路徑後的 Key
          });
      
          try {
              // 產生 Presigned URL,設定 300 秒 (5 分鐘) 有效
              const presignedUrl = await getSignedUrl(s3Client, command, {
                  expiresIn: 300, 
              });
      
              return {
                  statusCode: 200,
                  headers: {
                      "Content-Type": "application/json"
                  },
                  body: JSON.stringify({ 
                      downloadUrl: presignedUrl,
                      expiresIn: 300,
                      file: fileKey
                  }),
              };
          } catch (err) {
              console.error("S3 Error:", err);
              return {
                  statusCode: 500,
                  body: JSON.stringify({ message: "Failed to generate download link." }),
              };
          }
      };
      

    https://ithelp.ithome.com.tw/upload/images/20251004/20172743gyQXQ5xArQ.png

  6. 新增環境變數。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743iYmyhb7dTU.png

  7. 設定以下環境變數:
    DOWNLOAD_BUCKET:2025ithome-ducky
    FILE_PREFIX_PATH:ducky-test/
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743bpx6t2jJud.png

  8. 完成畫面。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743t74gnTlhoW.png

2. 授予Lambda的IAM Role權限(S3下載權限)

  1. 進入「IAM」頁面。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743mEuOkouCad.png

  2. 進入IAM role的頁面,點選該Lambda自動創建的IAM role。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743Ly0je9qf9c.png

  3. 新增「許可政策」。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743i9nnLJcs1t.png

  4. 增加S3的「GetObject」權限,指定授權的資源ARN。
    https://ithelp.ithome.com.tw/upload/images/20251004/201727437JXqmGH0vJ.png

  5. 設定「許可政策」名稱。
    https://ithelp.ithome.com.tw/upload/images/20251004/201727438c791qtC3H.png

    • S3的ARN在哪裡?
      https://ithelp.ithome.com.tw/upload/images/20251004/20172743C6PVDH2kMX.png
  6. 點選下一步。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743SLxlAlrReA.png

  7. 設定「許可政策」名稱。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743GeE7qrMPFm.png

  8. 完成畫面。
    https://ithelp.ithome.com.tw/upload/images/20251004/201727430yyTA01Mpb.png

3. 創建API Gateway新路徑,並整合Lambda

  1. 進入「API Gateway」服務頁面。
    https://ithelp.ithome.com.tw/upload/images/20251004/201727432ZzfyRKJ81.png

  2. 進入已創建的HTTP API Gateway。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743ier9Uwcp9q.png

  3. 創建一個新的路由路徑。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743IjKkSKoCGN.png

  4. 選用「GET」方式,並設定路徑。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743GpH9EMCs0v.png

  5. 設定身份驗證。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743XMlGMUiqTf.png

  6. 整合Lambda函數。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743QmNLbeyn79.png

  7. 選擇剛剛創建的Lambda函數,並整合。
    https://ithelp.ithome.com.tw/upload/images/20251004/20172743Px3FZ3sXIE.png

  8. 完成畫面。
    https://ithelp.ithome.com.tw/upload/images/20251004/201727436VscKjfPqX.png

3️⃣ 測試驗證

1. 透過終端機(terminal)向Cognito取得 session

  • 第一段

    aws cognito-idp initiate-auth \
      --auth-flow USER_PASSWORD_AUTH \
      --client-id <Cognito_APP_CLIENT_ID> \
      --auth-parameters USERNAME=<username>,PASSWORD=<password>
    

https://ithelp.ithome.com.tw/upload/images/20251004/201727439cmBp3azIR.png

2. 透過終端機(terminal),用 session向Cognito拿到對應的 IdTokenAccessToken

  • 第二段

    aws cognito-idp respond-to-auth-challenge \
      --client-id <Cognito_APP_CLIENT_ID> \
      --challenge-name SOFTWARE_TOKEN_MFA \
      --session <上一步回傳的Session> \
      --challenge-responses USERNAME=<username>,SOFTWARE_TOKEN_MFA_CODE=<六位數驗證碼>
    

https://ithelp.ithome.com.tw/upload/images/20251004/20172743eG3mK0a9h0.png

3. 使用 Access Token 獲得S3下載連結

  • 第三段

    # 假設您的 API Gateway 資源路徑是 /api-get-download-url
    # 檔案名稱要比照S3上的名稱
    curl -X GET -H "Authorization: Bearer YOUR_TOKEN" \
    "https://ldkd7gntre.execute-api.us-east-1.amazonaws.com/prod/api-get-download-url?file=report.pdf"
    

https://ithelp.ithome.com.tw/upload/images/20251004/20172743gA74SEorty.png

【補充】如果不帶Access Token,那將因「授權」被拒絕。
https://ithelp.ithome.com.tw/upload/images/20251004/2017274322RQrVR8SM.png

4. 透過下載的連結下載S3資源

  • 第四段

    # 範例:使用 -o 參數將檔案下載並存為指定檔名 「test.jpg」
    curl -o test.jpg "YOUR_DOWNLOAD_URL_HERE"
    

https://ithelp.ithome.com.tw/upload/images/20251004/20172743ECH0k9P35O.png

5. 成功取得一個限時連結,於 5 分鐘內可下載可用檔案。

  • 範例格式

    # 範例:使用 -o 參數將檔案下載並存為 report.pdf
    curl -o report.pdf "YOUR_DOWNLOAD_URL_HERE"
    

https://ithelp.ithome.com.tw/upload/images/20251004/20172743k65OJWL41r.png
https://ithelp.ithome.com.tw/upload/images/20251004/20172743nJNP3r6SNv.png

6. 測試超過 5 分鐘後,連結下載的檔案會失效。

https://ithelp.ithome.com.tw/upload/images/20251004/20172743W94eHyTeIT.png

六、結語

本篇實作了 檔案分享安全機制,結合 S3、Lambda、API Gateway 與 Cognito,讓系統可以安全地產生「限時連結」,並確保只有合法會員才能存取。此做法大幅降低了檔案外流風險,也同時兼顧了使用者體驗與企業等級安全性。


上一篇
Day 19 檔案後處理:S3 x Lambda x Step Functions 自動化壓縮與掃毒
下一篇
Day 21 聰明檔案管理:S3 Lifecycle x Lambda 自動化版本控制與清理
系列文
從一個網站的誕生,看懂 AWS 架構與自動化的全流程!24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言