認證 vs 授權:那個誰都能調的介面
這兩個詞聽起來很像,而二者的區別正是真正的資料洩露藏身之處。
- 認證(Authentication) 回答的是你是誰?——登入、證明身份。
- 授權(Authorization) 回答的是你被允許做什麼?——這個已登入的使用者是否可以對這份資料執行這個操作。
AI 在認證上還算不錯;庫幫它處理了大部分。授權才是它經常翻車的地方,因為授權是你應用規則所特有的,而 AI 並不知道這些規則。教科書級的災難:
// VULNERABLE: 檢查了你是否登入,卻沒檢查你是誰
app.get("/admin/export-all-users", requireLogin, (req, res) => {
res.json(db.getAllUsers()); // 任何已登入使用者都能打這個介面
});
那個介面是“受保護的”——你必須登入。但任何已登入的使用者,包括一個三十秒前才註冊的,都能呼叫它、下載到每一個使用者的資料。認證有了,授權沒有。修復辦法是在操作本身上檢查許可權:
// SAFE: 確認這個使用者確實是管理員
app.get("/admin/export-all-users", requireLogin, (req, res) => {
if (!req.user.isAdmin) return res.status(403).send("Forbidden");
res.json(db.getAllUsers());
});
把一個請求必須通過的兩道門想像出來。認證問"你登入了嗎?";授權問"是你被允許做這件事嗎?"。當一個應用建了第一道門、卻忘了第二道門時,入侵就發生了:
請求 ──▶ ┌──────────────────┐ ──▶ ┌──────────────────┐ ──▶ 執行
│ 認證(authn) │ │ 授權(authz) │ 動作
│ "已登入?" │ │ "該使用者能否 │
│ │ │ 做此操作?" │
└──────────────────┘ └──────────────────┘
✓ 多數應用 ✗ 常常缺失
都做了 → 即是漏洞
示例 — /admin/export-all-users
已登入使用者 ──▶ [auth ✓] ──▶ [ 無authz檢查 ] ──▶ 全部使用者被匯出
已登入使用者 ──▶ [auth ✓] ──▶ [ isAdmin? → 403 ] ──▶ 被攔截 ✓
同樣的陷阱以縮小版的形式到處出現:一個介面返回訂單 #1234,卻不檢查這個訂單是不是屬於發起請求的那個使用者。任何人都能改一改 裡的數字,讀到別人的訂單。規則一點也不光鮮,但絕對:在每一個接觸資料的介面上檢查授權,並且不要信任來自客戶端的 ID。 不要因為一個 URL 很隱蔽就假定它安全——“沒人知道這玩意兒存在”不是一種安全控制手段。