Skip to content
Go
访问控制模型大全

访问控制模型大全

本章给出 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.act
p, alice, data1, read
p, bob, data2, write
e.Enforce("alice", "data1", "read") // true
e.Enforce("alice", "data2", "read") // false

ACL 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.act
p, data_admin, data, read
p, data_admin, data, write

g, alice, data_admin
e.Enforce("alice", "data", "write") // true:alice 继承 data_admin 的权限

RBAC 角色可以多级继承

g, alice, admin
g, admin, superadmin     # 角色也可以继承角色
p, superadmin, data, write

g(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.act
p, admin, protected_data, read

g, alice, admin
g2, data1, protected_data    # data1 属于 protected_data 组
g2, data2, protected_data
e.Enforce("alice", "data1", "read") // true:data1 ∈ protected_data

RBAC + 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.act
p, admin, data, write, allow
p, alice, data, write, deny     # alice 虽是 admin,但被单独拒绝
g, alice, admin
e.Enforce("alice", "data", "write") // false:deny 一票否决
e.Enforce("bob", "data", "write")   // 取决于 bob 是否 admin

ABAC(基于属性)

请求参数可以传结构体,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.act
p, r.sub.Age > 18, /data1, read
p, r.sub.Age < 60 && r.sub.Level >= 3, /data2, write
type 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, eft
p, alice, data1, write, deny     # 排前面的先生效
p, data1_admin, data1, write, allow
g, alice, data1_admin

e.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.act
p, 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.act
p, alice, export_report
p, bob, send_email
e.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, viewer
e.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-匹配器与函数 的对照表。