权限判断从几个 if 变成散落的判断之后:Casbin 与自研 RBAC 的取舍
几乎所有后台系统都会走到这一步:一开始只有管理员和普通用户,代码里一个 if user.Role == "admin" 就完了。后来产品开始提更细的需求,运营能改商品、财务只能看订单不能删、客服只能查单、区域管理员只管自己的区域,多租户场景下还要按租户隔离数据。权限判断从一个 if 扩散成 Handler、Service、Middleware 里散落的判断。
这时候真正的问题已经不是"怎么判断管理员",而是:谁能对什么做什么,如何长期、统一地维护下去。
认证与授权是两件事
认证(Authentication)解决"你是谁",常见手段是 JWT、Session、OAuth。
授权(Authorization)解决"你能做什么",比如:
userId = 10001
GET /orders -> Allow
DELETE /orders -> Deny
Casbin 管的是 Authorization,不负责登录、不管用户体系、不是 OAuth Server。典型链路是:
Request -> JWT Auth -> 获取 userId -> Casbin 判断 -> Allow/Deny
ACL 是最基础的抽象
ACL(Access Control List)把权限抽象成三元组:
sub -> 谁
obj -> 对什么
act -> 做什么
对应到请求就是 alice, /orders, GET,表示 Alice 可以对订单执行读操作。
但 ACL 在真实项目里很难直接用。用户一多,逐条分配权限的维护成本就上去了。于是出现了 RBAC。
RBAC:用户 -> 角色 -> 权限
RBAC 的核心变化是中间加了角色层:
User -> Role -> Permission
admin -> order:read, order:create, order:update, order:delete
operator -> order:read, order:update
viewer -> order:read
用户只绑定角色:Alice -> admin,Bob -> operator。权限管理从"用户到权限"变成了"用户到角色再到权限"。
自己写 RBAC 会遇到什么
第一版很简单,三张表的事:user、role、permission,加中间表 user_role 和 role_permission,然后查用户有没有某个权限。
但需求不会停在第一版,很快会遇到这些:
角色继承。产品说 super_admin 自动拥有 admin 的权限,admin 又继承 operator。自己实现就要处理递归查询、循环继承、权限合并。Casbin 里直接声明角色关系即可,继承逻辑交给模型处理:
g, alice, admin
g, admin, operator
资源开始复杂。权限对象从 order:read 变成 /orders/:id 这种路径,还要区分 GET/POST/PUT/DELETE,甚至需要通配符和正则匹配。这些逻辑全部自己写,权限模块会逐渐变成一个规则引擎。
多租户。同一个用户在不同租户下角色不同:
Alice -> Tenant A -> admin
Alice -> Tenant B -> viewer
原来的 User -> Role 不够了,需要 User -> Tenant -> Role。Casbin 支持 domain RBAC,请求定义变成 r = sub, dom, obj, act,判断逻辑变成:
Alice, tenant-a, /orders, DELETE
除了上面这些,还有资源归属判断、ABAC 属性规则、多实例状态同步。到这一步,你其实已经在重新实现一个访问控制引擎,而不是在写权限表。
Casbin 的核心思想:Model 配置化
Casbin 做了一件很关键的事:把访问控制模型本身配置化。一个基础 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
[request_definition]定义一次权限判断包含什么[policy_definition]定义策略的结构[role_definition]定义角色关系[policy_effect]定义多个 policy 命中时的最终结果[matchers]定义匹配规则
一旦模型定了,业务代码只调 Enforce:
ok, err := enforcer.Enforce("alice", "/orders", "GET")
策略数据长这样:
p, admin, /orders, GET
p, admin, /orders, POST
g, alice, admin
Alice 的每次请求,Casbin 根据模型和策略算出 Allow 或 Deny。
把模型配置化的好处是,权限变化时不用改业务代码。今天只是 RBAC,明天要加 Tenant,后天要根据资源 Owner 做 ABAC,改的是模型和策略,不是藏在 Handler 里的 if。
Casbin 不能替代权限表
管理后台仍然需要用户、角色、菜单、权限这些表。角色列表、菜单树、用户角色分配这些功能都得有数据支撑。Casbin 替代的是运行时"是否允许"这个判断逻辑,不是权限数据的管理和展示。
什么时候不需要 Casbin
如果系统永远只有 admin 和 user 两种角色,权限判断就是:
if user.IsAdmin {
// allow
}
直接写更简单,引入 Casbin 是过度设计。
更精确的边界是:
- 权限模型固定不变、角色极少 -> 自己写
- 出现 Role + Resource + Action 的组合,或者 Tenant + Role + Resource + Action -> 考虑 Casbin
取舍
认证(Authentication)和业务规则适合自己掌控,因为它们和业务耦合度高。但通用 RBAC 判断涉及角色继承、路径匹配、多租户域隔离、策略演进,是一个成熟问题域,自己从头实现一个权限引擎大概率会踩坑。
对于准备长期维护的 Go 后台、SaaS 或微服务项目,RBAC 这类通用权限判断,没有必要再造轮子。