RBAC 授权
规则
- 在每个路由的中间件中检查权限——绝不使授权变为可选
- 默认拒绝:如果没有明确授予权限,则拒绝访问
- 服务端强制:隐藏 UI 元素不是安全措施,应在服务端进行检查
- 定义角色层级:admin > editor > viewer —— 高级角色继承低级权限
- 资源级检查:验证
can(user, "edit", resource) —— 而不仅仅是基于角色的访问
- 审计所有权限检查 —— 记录谁在何时访问了什么,尤其是管理操作
- 区分身份验证(你是谁?)与授权(你能做什么?)
type Action = "read" | "create" | "update" | "delete";
type Resource = { ownerId: string; [key: string]: any };
function can(user: { id: string; role: string }, action: Action, resource?: Resource): boolean {
const permissions: Record<string, Action[]> = {
admin: ["read", "create", "update", "delete"],
editor: ["read", "create", "update"],
viewer: ["read"],
};
const allowed = permissions[user.role];
if (!allowed?.includes(action)) return false;
if (resource && action !== "read" && user.role !== "admin") {
return resource.ownerId === user.id;
}
return true;
}
function requirePermission(action: Action) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ error: "未认证" });
if (!can(req.user, action, req.resource)) {
return res.status(403).json({ error: "禁止访问" });
}
next();
};
}
app.put("/api/posts/:id", loadResource, requirePermission("update"), updatePost);
避免
- 仅在 UI 中检查权限——攻击者可以直接调用你的 API
- 除非明确拒绝才允许访问——始终默认拒绝
- 使用单一的
isAdmin 布尔值——角色应细粒度且可扩展
- 忘记资源级检查——"editor" 不应意味着"可以编辑所有人的内容"
- 在"内部"端点上跳过授权——这些端点最终会公开