檔案上傳系統設計 (10) —— 私有檔案的訪問控制
如果你開發的是像「大頭貼」或「產品插圖」這樣的功能,公網直接訪問路徑(https://cdn.example.com/uploads/me.jpg)是沒問題的。
但如果你處理的是「合約 PDF」、「用戶的身分證掃描」或者是「付費課程影片」,事情就完全不一樣了。你絕對不能讓任何知道路徑的人都能下載這些檔案。
本篇將介紹如何從架構層面實作 私有檔案的訪問控制 (Access Control)。
一、 核心概念:Public vs Private Storage
第一步是將開發思維進行隔離。
- Public Bucket:存放一般靜態資源,讀取權限設為
public-read。 - Private Bucket:存放敏感檔案,讀取權限設為
private,拒絕所有匿名請求。
二、 方案 A:預簽名訪問 URL (Cloud Native)
這是利用雲端服務(如 S3/GCS)最優雅的做法。
運作流程:
- 檔案儲存在 S3,設為私有。
- 前端請求查看照片。
- 後端 API 檢查該用戶是否有權限。
- 如果授權通過,後端向 S3 申請一個有效期(例如 5 分鐘)的 Signed Download URL。
- 前端拿到這組帶有加密簽章的 URL 即可直接加載圖片。
> **優點**:不消耗你的伺服器頻寬,且 URL 過期後即失效,防止用戶私下傳送連結。
三、 方案 B:應用程式代傳 (Proxy Streaming)
如果你你是使用本地硬碟儲存,或不想暴露 S3 資訊,可以使用轉傳模式。
實作範例 (Node.js):
javascript
app.get("/private/:fileId", async (req, res) => {
// 1. 權限檢查
const userHasPermission = await checkAuth(req.user, req.params.fileId);
if (!userHasPermission) return res.status(403).send("拒絕訪問");
// 2. 獲取檔案路徑
const file = await db.files.find(req.params.fileId);
const filePath = path.resolve(file.full_path);
// 3. 串流輸出
const stream = fs.createReadStream(filePath);
res.setHeader("Content-Type", file.mime_type);
stream.pipe(res);
});四、 進階:高效的安全轉發 (X-Accel-Redirect)
上述「方案 B」的 Node.js 串流雖然簡單,但會佔用 Node.js 的 CPU 與運算資源。
如果你使用 Nginx,可以使用一個強大的技巧:X-Accel-Redirect。
- Node.js 處理 API,檢查權限。
- 權限通過後,Node.js 回傳一個 Header:
X-Accel-Redirect: /internal/file/path。 - Nginx 攔截此 Header,並「在內部」直接從硬碟讀取檔案傳給用戶。
結果:Node.js 負責「安全門禁」,Nginx 負責「搬運工」。這能讓單機效能發揮到極致。
總結
保護私有檔案的原則:
- 儲存隔離:不要把私有檔案跟公開檔案混在一起。
- 門禁制度:每一次訪問都必須經過 API 的身份驗證。
- 時效授權:儘量使用帶有效期的簽名連結。
至此,我們完成了本系列第二階段「整合實戰與體驗」。目前你的系統已經具備了強大的上傳、處理、優化與保護能力。
️ 進階挑戰
嘗試使用 JWT 或 Session 機制實作一個簡單的檔案下載門禁。如果 Token 過期,伺服器應該回傳什麼樣的 HTTP 狀態碼?
延伸閱讀與資源
- Nginx Wiki: X-Accel
- Auth0: Securing file uploads