访问控制模型大全
本章给出 Casbin 支持的所有主流访问控制模型的 model + policy + 代码 三件套,可直接复制到 Casbin Editor 验证。每个模型都标注了适用场景,选型时对照第一节的决策表。
模型选型决策表
| 需求 | 推荐模型 |
|---|---|
| 用户少、权限直接挂用户 | ACL |
| 需要超级管理员绕过一切检查 | ACL with superuser |
| 用户 → 角色 → 权限(绝大多数后台系统) | RBAC |
| 多租户/SaaS:同一用户在不同租户角色不同 | RBAC with domains(见 03-RBAC与多租户) |
| 权限取决于属性(如"只能改自己的文章") | ABAC |
| 保护 REST API(路径通配 + HTTP 方法) | RESTful(keyMatch) |
| 白名单为主 + 个别拒绝 | RBAC + deny-override |
| 规则有优先级、后加规则覆盖旧规则 | Priority |
💡 最佳实践:模型是可以组合的。生产中最常见的其实是 “RBAC + RESTful + superuser” 三合一(本章最后一节)。
ACL(访问控制列表)
权限直接授予用户,一人一条。
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.actp, alice, data1, read
p, bob, data2, writee.Enforce("alice", "data1", "read") // true
e.Enforce("alice", "data2", "read") // falseACL with superuser(超级管理员)
root 不需要任何策略,直接放行 —— 写在 matcher 里而不是策略里:
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"e.Enforce("root", "任何东西", "任何操作") // 永远 true💡 超管判断放 matcher(模型层)而非业务代码里,保证所有走 Casbin 的入口行为一致,且审计时一目了然。
RBAC(基于角色)
最常用模型,详细的 API 与进阶用法见 03-RBAC与多租户。
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actp, data_admin, data, read
p, data_admin, data, write
g, alice, data_admine.Enforce("alice", "data", "write") // true:alice 继承 data_admin 的权限RBAC 角色可以多级继承
g, alice, admin
g, admin, superadmin # 角色也可以继承角色
p, superadmin, data, writeg(alice, superadmin) 传递成立,alice 可写 data。
资源分组(g2)
不仅用户可以分组,资源也可以分组——用第二个分组关系 g2:
[role_definition]
g = _, _
g2 = _, _
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.actp, admin, protected_data, read
g, alice, admin
g2, data1, protected_data # data1 属于 protected_data 组
g2, data2, protected_datae.Enforce("alice", "data1", "read") // true:data1 ∈ protected_dataRBAC + deny-override(白名单 + 一票否决)
[policy_definition]
p = sub, obj, act, eft
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actp, admin, data, write, allow
p, alice, data, write, deny # alice 虽是 admin,但被单独拒绝
g, alice, admine.Enforce("alice", "data", "write") // false:deny 一票否决
e.Enforce("bob", "data", "write") // 取决于 bob 是否 adminABAC(基于属性)
请求参数可以传结构体,matcher 直接访问其字段 —— 无需任何策略行也能判定:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == r.obj.Owner # 资源的 Owner 是自己 → 放行type Article struct {
ID int
Owner string
}
a := Article{ID: 1, Owner: "alice"}
e.Enforce("alice", a, "edit") // true:a.Owner == "alice"
e.Enforce("bob", a, "edit") // false🚨 陷阱:matcher 访问的结构体字段必须是可导出的(大写开头),r.obj.owner 取不到值。传 map 不行,必须是 struct(或实现相应接口的类型)。
ABAC with eval():把规则写进策略
上面的 ABAC 规则硬编码在 matcher 里。用 eval() 可以把规则本身作为策略数据,运行时增删:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj, act # 第一个字段存的是"规则表达式"
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.actp, r.sub.Age > 18, /data1, read
p, r.sub.Age < 60 && r.sub.Level >= 3, /data2, writetype User struct {
Name string
Age int
Level int
}
e.Enforce(User{Name: "alice", Age: 25, Level: 5}, "/data1", "read") // true
e.Enforce(User{Name: "tom", Age: 16, Level: 1}, "/data1", "read") // false🔬 深入原理:eval(p.sub_rule) 会把策略字段的内容当作 govaluate 表达式动态编译执行,请求上下文(r.sub 等)对表达式可见。这让 Casbin 变成一个小型规则引擎——策略不再只是数据,也可以是逻辑。代价是性能(每次都要解析表达式)与安全(谁能写策略谁就能写逻辑,策略管理入口必须严控)。
RESTful 模型(保护 HTTP API)
用 keyMatch2 匹配路径通配、regexMatch 匹配 HTTP 方法(完整函数列表见 06-匹配器与函数):
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)p, alice, /api/users/:id, GET
p, alice, /api/articles/*, "(GET)|(POST)"
p, bob, /api/*, ".*"e.Enforce("alice", "/api/users/42", "GET") // true::id 匹配 42
e.Enforce("alice", "/api/articles/1/like", "POST") // true:* 匹配多级
e.Enforce("alice", "/api/users/42", "DELETE") // false🚨 陷阱:CSV 中含逗号的值(如 "(GET)|(POST)")必须加双引号,否则被拆成两列。
Priority(优先级模型)
隐式优先级:按策略顺序
[policy_effect]
e = priority(p.eft) || deny
[policy_definition]
p = sub, obj, act, eftp, alice, data1, write, deny # 排前面的先生效
p, data1_admin, data1, write, allow
g, alice, data1_admine.Enforce("alice", "data1", "write") → false:第一条 deny 先匹配,直接定案。
🚨 陷阱:隐式优先级依赖策略加载顺序。文件 adapter 顺序稳定,数据库 adapter 不保证 —— 生产环境用显式优先级。
显式优先级:priority 字段
[policy_definition]
p = priority, sub, obj, act, eft
[policy_effect]
e = priority(p.eft) || deny
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actp, 10, data1_deny_group, data1, write, deny
p, 20, alice, data1, write, allow
g, alice, data1_deny_group数字越小优先级越高。alice 同时命中优先级 10 的 deny 和 20 的 allow → deny 生效。
无资源模型(只判定"谁能做什么操作")
有些场景没有具体资源,只有动作(如"能否导出报表"):
[request_definition]
r = sub, act
[policy_definition]
p = sub, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.act == p.actp, alice, export_report
p, bob, send_emaile.Enforce("alice", "export_report") // true组合实战:RBAC + RESTful + superuser + deny
生产后台系统的"缝合怪"模型,四种能力一次到位:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = (g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)) || r.sub == "root"p, admin, /api/*, ".*", allow
p, editor, /api/articles/*, "(GET)|(POST)|(PUT)", allow
p, viewer, /api/*, GET, allow
p, blocked_user, /api/*, ".*", deny
g, alice, admin
g, bob, editor
g, carol, viewere.Enforce("root", "/internal/debug", "DELETE") // true:超管
e.Enforce("alice", "/api/users/1", "DELETE") // true:admin 全通
e.Enforce("bob", "/api/articles/9", "PUT") // true:editor
e.Enforce("bob", "/api/users/1", "GET") // false:editor 无 users 权限
e.Enforce("carol", "/api/articles/9", "GET") // true:viewer 只读常见陷阱
🚨 模型抄错一个字段全盘皆输:matcher 里 g(r.sub, p.sub) 写成 r.sub == p.sub,RBAC 立刻退化成 ACL,所有走角色的请求全部 false。改模型后务必用 Editor 或单测验证核心用例。
🚨 ABAC 传参类型:r.obj.Owner 要求传入的是结构体且字段可导出;传 string 会报 “cannot access field”。同一个 r.obj 不能既当 string 用(keyMatch)又当 struct 用(.Owner)——需要就拆成两个请求字段。
🚨 deny 模型忘了改 effect:只在策略里加 deny 而 effect 还是 some(where (p.eft == allow)),deny 行会被当作普通不匹配忽略(因为 p.eft == allow 不成立),拒绝完全不生效。deny 必须配 !some(where (p.eft == deny))。
🚨 priority 模型下随手 AddPolicy:显式优先级模型中忘传 priority 字段、或多条策略同优先级时行为依赖加载顺序。约定优先级号段(如 0-99 系统保留、100+ 业务)可避免踩坑。
🚨 keyMatch2 的 * 只在 pattern 末尾才匹配多级:/api/* 能匹配 /api/a/b,但 /api/*/detail 的 * 不跨 /。规则见 06-匹配器与函数 的对照表。