~/VibeHandbook
免费 PDF

18 · 04

认证 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 很隐蔽就假定它安全——“没人知道这玩意儿存在”不是一种安全控制手段。

想离线阅读?

免费下载整本书的 PDF 或 EPUB。