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