认证与授权
证明用户是谁(认证)并决定他们能做什么(授权)——每个请求都正确。
为什么
认证和授权是任何应用中最安全关键的代码和最常见入侵源。单个跳过授权检查的端点(因后加开发者复制了公开路由)让任何用户读任何其他用户数据。两者常被混为一谈——"用户登录了所以能访问"——但登录(认证)只证身份;不说此用户能否访问此特定资源。正确授权是每请求每资源:认证用户须拥有或有权访问他们 请求的具体对象。大规模下弄错意味着一个 bug 暴露每个用户数据非仅一个。
何时使用
第一个端点前设计认证/授权模型。合并前审查每个新路由的授权。每季审计完整授权矩阵——它是最可能静默漂移的代码。
如何做
1. 分开认证(谁)和授权(什么)——绝不混为一谈 - 认证: 验 JWT/session cookie,加载用户身份 - 授权: 查此身份是否有权访问此特定资源 2. 认证: 用久经考验的库,别自己造 - NextAuth/Auth.js、Passport、Keycloak 或云 IAM(Cognito、Auth0) - 密码哈希用 bcrypt(cost=12) 或 argon2id——绝不用 MD5/SHA1 - 发短寿 JWT(15 分钟)+ 刷新令牌,绝无永久会话 3. 授权: 每请求每资源在服务端查 - 每个端点: 加载资源,查 resource.ownerId === user.id(或角色检查) - 绝不信客户端过滤——客户端可被 curl 绕过 - 用中间件或装饰器使检查不会被忘 4. 穷尽测试授权矩阵 - 每个端点测: 拥有者可访问、非拥有者不可、匿名不可 - 用参数化测试遍历每个路由 × 每个角色 - 每端点一条"非拥有者得 403"测试抓大多 IDOR bug 5. 审计和轮换 - 每季: 审每个路由授权,找漂移(丢了检查的路由) - 定期轮换密钥和秘密;改密时撤销会话 - 记每个授权拒绝——403 飙升可能指示攻击或 bug
示例
// src/middleware/authz.ts — 每请求每资源授权
import type { Request, Response, NextFunction } from "express";
export function requireOwnership(getResourceId: (req: Request) => string) {
return async (req: Request, res: Response, next: NextFunction) => {
const userId = req.user?.id; // 认证中间件设
if (!userId) return res.status(401).json({ error: "Unauthorized" });
const resourceId = getResourceId(req);
const resource = await db.resources.findById(resourceId);
if (!resource) return res.status(404).json({ error: "Not found" });
if (resource.ownerId !== userId && req.user.role !== "admin") {
return res.status(403).json({ error: "Forbidden" }); // 非拥有者拒
}
req.resource = resource;
next();
};
}
// 路由: 仅拥有者可删其资源
router.delete("/resources/:id", requireOwnership((r) => r.params.id), async (req, res) => {
await db.resources.delete(req.resource.id);
res.status(204).end();
});
// 测试: 非拥有者得 403
it("拒绝非拥有者", async () => {
const res = await request(app).delete("/resources/42").set("auth", tokenForUser(99));
expect(res.status).toBe(403); // 42 属用户 1 非 99
});常见问题
- ⚠只查用户登录了(认证)不查他们拥有该具体资源(授权)——这是经典 IDOR 漏洞,用户 A 改 URL 中 ID 读用户 B 数据。
- ⚠客户端做授权(非拥有者藏编辑按钮)服务端不做——服务端端点无保护,任何人用 curl 可编辑任何资源。
- ⚠该用拥有检查处用角色检查(isAdmin)——仅管理员门控挡非管理员但让任何管理员编辑任何资源,若管理员应只读非改则错。
- ⚠新端点因复制公开路由而忘授权检查——路由无保护发布,bug 被攻击者或幸运用户发现非团队。
最佳实践
- ✓在单一中间件或装饰器中强制授权,每个受保护路由必须用——lint 无保护 handler 使加路由不可能不带检查。
- ✓每端点测三例: 拥有者成功、非拥有者 403、匿名 401——所有路由参数化测试抓住丢了检查的那条。
- ✓服务端加载资源查拥有,绝不信客户端——客户端是优化(藏按钮),服务端是安全边界。
- ✓记每个 403 含用户 ID、资源 ID、动作,飙升告警——拒绝突增可能指示攻击探测 IDOR 或新路由 bug。