Authorization - Casbin Access Control Models
核心架构
1 | flowchart TB |
核心特性
将授权逻辑与业务代码分离,通过配置文件实现复杂的权限模型(RBAC、ABAC 等)
混合访问控制模型
Casbin 基于 PERM 元模型 (Policy, Effect, Request, Matchers) 来定义访问控制规则
| 组件 | 描述 |
|---|---|
| Request | 访问请求的结构(谁、想做什么、 ресурс) |
| Policy | 权限规则定义 |
| Matchers | 匹配规则,决定请求如何对应到策略 |
| Effect | 最终效果(允许/拒绝) |
通过修改 CONF 配置文件即可切换授权机制,无需改动代码
灵活的策略存储
支持多种存储方式
| 存储 | 描述 |
|---|---|
| 内存 | 用于测试 |
| 文件 | JSON/YAML/CSV |
| 数据库 | MySQL、Postgres、MongoDB、Redis、Cassandra、S3 |
跨语言跨平台
- 各语言实现相同的 API 和行为
- Go、Java、PHP、Node.js、Python、.NET、Rust 等
扩展机制
Casbin 核心保持轻量,策略存储、角色管理都通过插件化扩展,按场景选择合适的 Adapter 和 Role Manager
Policy 持久化
通过 Adapter(适配器) 将策略存储与核心库分离
- 核心库只负责策略执行
- 存储逻辑由独立的 Adapter 实现
- 支持文件、数据库、S3 等多种后端
- 默认内置文件 Adapter,其他需第三方提供
大规模策略执行
当策略数据量很大时,全量加载效率低下
- Filtered Policy Loading - 支持从存储中只加载符合过滤条件的策略子集
- 典型场景:多租户应用、角色分级等
- 避免内存溢出,提升性能
Role Manager
专门处理 RBAC 角色层级(用户-角色映射)
- 可从 Casbin 策略规则中加载角色关系
- 也可从外部源(LDAP、Okta、Auth0、Azure AD 等)同步
- 同样与核心库分离,按需选用
Overview
Casbin 是一个高效的开源访问控制库,支持多种授权模型
核心组件
Enforcer 根据 Model 的规则,用 Policy 中的定义,来判断每个请求是否被允许
| 组件 | 作用 |
|---|---|
| Model | 定义授权逻辑(布局、执行流程、条件) |
| Policy | 定义具体规则(谁、对什么资源、做什么操作) |
| Enforcer | 核心执行器,用 Model + Policy 评估每个请求 |
1 | flowchart LR |
简单请求
请求 → Enforcer → 匹配 Model 和 Policy → 允许/拒绝
核心特点
| 特点 | 描述 |
|---|---|
| 规则驱动 | 在 Policy 文件中定义 主体-对象-操作 规则 |
| 格式灵活 | Policy 文件可以是 JSON、YAML、CSV 等任意格式 |
| 模型控制 | Model 文件让管理员完全掌控授权逻辑 |
| 跨语言一致 | 所有语言实现(Go/Java/Python 等)行为统一 |
语言支持
生产级状态
Go、Java、Node.js、PHP、Python、C#、C++、Rust
核心功能对比
Casbin 目标实现全语言功能对等,但多线程、Watcher、Role Manager 等高级功能尚未完全统一
| 功能 | 说明 |
|---|---|
| Enforcement | 请求拦截与决策(全部支持) |
| RBAC | 基于角色的访问控制(全部支持) |
| ABAC | 基于属性的访问控制(全部支持) |
| Adapter | 策略存储适配器(Delphi/Lua/Dart 不支持) |
| Role Manager | 角色管理层(Delphi 不支持) |
| Multi-Threading | 多线程支持(仅 Go/Java/Python/Rust 支持) |
定义
Casbin 是一个授权库,处理标准的
{ subject, object, action }访问控制模型,也支持 RBAC、ABAC 等复杂场景
| 元素 | 含义 | 示例 |
|---|---|---|
| Subject | 请求方(用户或服务) | alice, service-account |
| Object | 资源(被访问的对象) | file.txt, /api/users |
| Action | 操作(对资源的动作) | read, write, delete |
谁(subject)对什么(object)做什么操作(action)是否被允许?
能力
| 功能 | 说明 |
|---|---|
| 策略执行 | 在 { subject, object, action } 格式或自定义格式下执行允许/拒绝 |
| 存储管理 | 管理访问控制模型和策略的存储 |
| 角色层级 | 处理用户-角色、角色-角色关系(RBAC 角色继承) |
| 超级用户 | 内置超级用户(如 root)拥有无限制访问权限 |
| 模式匹配 | 内置匹配运算符(如 keyMatch 支持 /foo* 模式) |
非能力
应用已有用户管理系统,Casbin 专注于授权决策,不涉及身份认证
| 功能 | 说明 |
|---|---|
| 用户认证 | 不验证用户名/密码等登录凭证 |
| 用户/角色管理 | 不维护用户列表或角色列表 |
Get Started
Installation
1 | go get github.com/casbin/casbin/v3 |
Example
| 元素 | 角色 |
|---|---|
| Model | 定义规则结构 |
| Adapter | 提供策略存储 |
| Policy | 定义具体权限 |
| Enforcer | 执行权限判断 |
model.conf
1 | [request_definition] |
policy.csv
1 | p, alice, data1, read |
main.go
1 | package main |
Enforcer 是核心组件,需要 Model + Adapter 两个配置
1 | # 方式一:文件方式(简单场景) |
运行结果
1 | ✅ alice 可以 read data1 |
How It Works
PERM 四个组件共同定义授权逻辑
1 | flowchart LR |
Request 定义请求格式 → Policy 定义权限格式 → Matchers 定义如何匹配 → Effect 定义最终允许/拒绝
| 组件 | 作用 | 示例 |
|---|---|---|
| Request | 定义请求参数结构 | r = sub, obj, act |
| Policy | 定义策略规则结构 | p = sub, obj, act |
| Matchers | 请求与策略的匹配逻辑 | r.sub == p.sub && r.obj == p.obj && r.act == p.act |
| Effect | 多个策略匹配后的最终效果 | some(where (p.eft == allow)) |
Effect 表达式
1 | # 匹配到任意一个 allow 就允许 |
Model (model.conf)
1 | [request_definition] |
Policy (policy.csv)
1 | p, alice, data1, read # alice 可以读 data1 |
匹配过程
1 | alice 读 data1 |
1 | flowchart TB |
Matchers 多行写法
1 | [matchers] |
in 操作符(仅 Go)
1 | m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3') |
Access Control Models
Casbin 支持业界主流的访问控制模型,RBAC 和 ABAC 最常用,内置模型如下
| 模型 | 说明 |
|---|---|
| ACL | 基础访问控制列表(用户-资源-操作) |
| RBAC | 基于角色的访问控制(支持角色层级) |
| ABAC | 基于属性的访问控制(用户属性、资源属性、环境属性) |
| ReBAC | 基于关系的访问控制(实体间关系) |
| PBAC | 基于策略的访问控制 |
| MAC | 强制访问控制(多级安全) |
| OrBAC | 组织-Based 访问控制 |
| UCON | 使用控制模型(包含义务和条件) |
特色功能
| 功能 | 说明 |
|---|---|
| Priority Model | 支持策略优先级,优先级高的生效 |
| Super Administrator | 超级管理员(如 root)拥有无限制访问权限 |
| RBAC with Domains | 多租户/多域场景的角色管理 |
Supported Models
从简单 ACL 到复杂 BLP/LBAC,Casbin 覆盖了几乎所有访问控制模型
ACL 系列
| 模型 | 说明 | 适用场景 |
|---|---|---|
| ACL | 基础模型:用户-资源-操作 | 简单权限管理 |
| ACL with superuser | 支持超级管理员(如 root) | 需要最高权限账号 |
| ACL without users | 无用户概念,适合 API/设备访问 | 物联网、API 网关 |
| ACL without resources | 按资源类型而非实例授权 | “写文章”权限而非”写某篇文章” |
RBAC 系列
| 模型 | 说明 | 适用场景 |
|---|---|---|
| RBAC | 基础角色模型 | 常规权限管理 |
| RBAC with resource roles | 用户和资源都有角色 | 部门资源隔离 |
| RBAC with domains | 多域/多租户角色 | SaaS、跨国企业 |
ABAC 及高级模型
| 模型 | 说明 |
|---|---|
| ABAC | 用属性(如 resource.Owner)做判断 |
| PBAC | 动态上下文策略 |
| BLP | 机密性模型(Bell-LaPadula) |
| Biba | 完整性模型(防篡改) |
| LBAC | 机密性+完整性 lattice 模型 |
| OrBAC | 多组织抽象层 |
| UCON | 持续授权、可变属性、义务、条件 |
专用模型
| 模型 | 说明 |
|---|---|
| RESTful | 路径模式匹配 /res/*, HTTP 方法控制 |
| IP Match | IP 地址或 CIDR 网段控制 |
| Deny-override | deny 优先于 allow |
| Priority | 规则有序,先匹配先赢 |
Model Syntax
- Casbin 的 Model 就是一份声明式的权限元模型
- 用配置描述”怎么问(request)、怎么配(policy)、怎么判(matcher)、怎么组合(effect)“
- 切换权限模型只需换 .conf 文件,Enforce() 调用保持不变,实现了权限逻辑与业务代码的彻底解耦
- Casbin 用一个 .conf 文件声明式地描述整个权限模型,核心是 PERM 框架
整体结构 - 一个 Model 文件必须有 4 个段落,RBAC 场景可额外加 1~2 个
| 段落 | 是否必需 | 用途 |
|---|---|---|
| [request_definition] | 必须 | 定义请求入参 |
| [policy_definition] | 必须 | 定义策略规则结构 |
| [policy_effect] | 必须 | 多条策略命中时如何组合结果 |
| [matchers] | 必须 | 请求与策略的匹配表达式 |
| [role_definition] | RBAC 必须 | 定义角色继承关系 |
| [constraint_definition] | 可选 | RBAC 约束(职责分离等) |
Request Definition - 请求定义
1 | [request_definition] |
定义 e.Enforce(…) 的入参签名,经典三元组
| 元祖 | 说明 |
|---|---|
| sub | 主体(谁) |
| obj | 客体(操作什么资源) |
| act | 动作(做什么) |
可以自定义:不需要资源就写 r = sub, act,两个主体就写 r = sub, sub2, obj, act
Policy Definition - 策略定义
1 | [policy_definition] |
描述策略文件的列结构,策略文件中的每一行:
1 | p, alice, data1, read → 绑定 (p.sub=alice, p.obj=data1, p.act=read) |
关键点
- 每行第一个 token 是策略类型(p, p2),**必须与定义匹配**
- 所有元素都是字符串,不做类型转换
- p.eft 字段是策略效果(allow/deny),**省略时默认 allow**
Policy Effect - 策略效果
当多条策略同时命中时,如何组合结果,Casbin 内置了 5 种效果,**不支持自定义表达式**,只能用以上内置的
| 表达式 | 含义 | 适用场景 |
|---|---|---|
| some(where (p.eft == allow)) | allow-override:任一允许即允许 | ACL, RBAC 等 |
| !some(where (p.eft == deny)) | deny-override:任一拒绝即拒绝 | 安全优先场景 |
| some(where (p.eft == allow)) && !some(where (p.eft == deny)) | allow-and-deny:需至少一个 allow 且无 deny | 严格场景 |
| `priority(p.eft) | deny` | |
| subjectPriority(p.eft) | 角色优先级:按角色层级判断 | 基于角色的优先级 |
Constraint Definition - 约束定义(可选)
这是 RBAC 的高级特性,在角色分配变更时强制检查约束,防止违反安全规则,有 4 种约束类型,执行时机如下
- 在 AddGroupingPolicy() / RemoveGroupingPolicy() 时检查,违反约束则操作失败、策略不变
- 模型加载时也会全量校验现有数据
静态职责分离 (sod)
同一用户不能同时持有两个角色,例如:申请人和审批人不能是同一个人
1 | c = sod("finance_requester", "finance_approver") |
基数限制 (sodMax)
从一个角色集合中,同一用户最多只能持有 N 个,例如:薪资的查看、编辑、审批三个角色,一个人最多只能有一个
1 | c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1) |
角色基数 (roleMax)
一个角色最多只能分配给 N 个用户,例如:超级管理员最多 2 人
1 | c3 = roleMax("superadmin", 2) |
前置角色 (rolePre)
必须先持有前置角色才能被分配目标角色,例如:必须有安全培训认证才能成为数据库管理员
1 | c4 = rolePre("db_admin", "security_trained") |
Matchers - 匹配器
定义请求参数如何与策略字段匹配,支持算术
(+ - * /)和逻辑运算符(&& || !)
1 | [matchers] |
性能关键:表达式顺序
原则:把廉价条件放前面,昂贵操作(如 g() 角色查找)放后面,利用短路求值减少计算量
慢写法
角色查找在前 - 测试中 jasmine 有 2500 个角色,g() 先执行导致 2500 次角色遍历,耗时 6.16 秒
1 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act |
快写法
简单的字段比较在前 - 先用 r.obj == p.obj 过滤掉大量不匹配的策略,再做昂贵的角色查找,耗时仅 7.29 ms,快了约 800 倍
1 | m = r.obj == p.obj && g(r.sub, p.sub) && r.act == p.act |
多组定义(Multiple Section Types)
可以定义多套 r/p/e/m,用数字后缀区分 - 适用于同一系统中有多种权限模型的场景(比如普通用户用 RBAC,管理员用 ABAC)
1 | r2 = sub, obj, act |
使用时通过 EnforceContext 指定用哪一套
1 | ctx := NewEnforceContext("2") // 使用 r2, p2, e2, m2 |
in 操作符
判断左侧值是否在右侧数组中(严格 ==,无类型转换),适用于类似”用户是否在管理员列表中”的场景
1 | m = r.sub.Name in (r.obj.Admins) |
表达式引擎
Casbin 底层依赖各语言的表达式引擎来解析 matcher - 不同语言的引擎能力有差异,如果需要跨语言兼容,只用文档中描述的标准语法
| 实现 | 语言 | 引擎 |
|---|---|---|
| Casbin | Go | govaluate |
| jCasbin | Java | aviatorscript |
| Node-Casbin | Node.js | expression-eval |
| PyCasbin | Python | simpleeval |
Effector
Effector 是权限判定流程中的最后一步,负责把多条匹配策略的结果合并为一个最终决定
核心概念
- 当一次 Enforce() 调用时,可能同时命中多条策略(比如一条 allow、一条 deny),Effector 就是决定”怎么拍板“的组件
- 请求 → Matcher 匹配 → 多条策略命中,各有效果 → Effector 合并 → 最终决定
MergeEffects() - 合并效果
这是 Effector 的核心方法
1 | Effect, explainIndex, err = e.MergeEffects(expr, effects, matches, policyIndex, policyLength) |
参数
| 参数 | 含义 |
|---|---|
| expr | 策略效果表达式,即 model 中 [policy_effect] 段定义的字符串,如 some(where (p.eft == allow)) |
| effects | 有匹配策略的效果切片,每个元素是 Allow、Deny 或 Indeterminate(不确定) |
| matches | 布尔数组,标记哪些策略被匹配到了 |
| policyIndex | 当前评估到第几条策略 |
| policyLength | 策略总数 |
返回值
| 返回值 | 含义 |
|---|---|
| Effect | 最终决定:Allow、Deny 或 Indeterminate |
| explainIndex | 是哪条策略决定了结果(用于审计/调试),无法确定时为 -1 |
| err | 如果效果表达式不支持,返回错误 |
合并逻辑举例
以 allow-override 为例
(some(where (p.eft == allow)))
1 | 策略1: allow ← 命中 |
effects = [Allow, Deny],Effector 的逻辑:只要有任意一条 allow,且没有 deny → 最终 allow- 实际结果是 allow
不同效果表达式的合并规则
| 效果类型 | 合并逻辑 |
|---|---|
| allow-override | 任一 allow → allow |
| deny-override | 任一 deny → deny |
| allow-and-deny | 至少一个 allow 且无 deny → allow |
| priority | 按策略顺序,先命中者胜出 |
短路优化
除非效果表达式是
priority(p_eft) || deny,否则 enforcer 可以短路:一旦结果确定,剩余策略就不再评估
1 | 效果表达式: some(where (p.eft == allow)) // allow-override |
- 这就是为什么 policyIndex 可能小于
policyLength - 1- 说明后面的策略被跳过了 - 对于 priority 效果则不能短路,因为必须按顺序遍历找到优先级最高的那条
自定义 Effector
可以实现自己的 Effector 接口,定义自定义的合并语义 - 适用于内置效果表达式无法满足的场景
1 | var e Effector // 自定义实现 |
- Casbin 内置的 Default Effector 已经能处理所有内置效果表达式
- 只有需要完全自定义的合并逻辑时,才需要实现自己的 Effector
与 Model 中 Policy Effect 的关系
Model 中的
[policy_effect]和这里的 Effector 是同一事物的两面
| 维度 | 说明 |
|---|---|
| Model 层 | 用声明式字符串描述效果语义(如 some(where (p.eft == allow))) |
| 代码层 | Effector 组件实际执行这个语义,遍历效果切片并产出最终决定 |
Functions
让 Matcher 的表达能力从简单的 == 比较扩展到了模式匹配、正则、IP 网段、路径参数提取等丰富的场景,同时保持配置文件的声明式风格
| 类别 | 描述 |
|---|---|
| 匹配函数(keyMatch 系列) | 解决”请求的 URL 是否匹配策略中的路径模式“ |
| 提取函数(keyGet 系列) | 解决”从 URL 中提取参数值供后续使用“ |
| 自定义函数 | 解决内置函数覆盖不到的业务特定匹配逻辑(如时间范围、属性比较等) |
内置函数总览
Matcher 不只能写 ==、&& 这种简单表达式,还可以调用内置函数来处理复杂的匹配场景,分两大类
Key Matching - 键匹配函数(返回 bool)
用于判断 URL/路径是否匹配某个模式
| 函数 | 通配符风格 | 示例 pattern | 典型场景 |
|---|---|---|---|
| keyMatch | * | /alice_data/* | 简单通配符,* 匹配剩余所有字符 |
| keyMatch2 | :name | /alice_data/:resource | 类似 Express/Flask 的路径参数 |
| keyMatch3 | {name} | /alice_data/{resource} | 类似 OpenAPI/Spring 的路径参数 |
| keyMatch4 | {name} | /data/{id}/book/{id} | 同 keyMatch3 但支持同名参数一致性校验(两个 {id} 必须相同) |
| keyMatch5 | {name} + * | /data/{id}/* | keyMatch3 和 keyMatch 的混合,还支持 query string |
| regexMatch | 正则表达式 | ^/alice_.*$ | 任意正则匹配 |
| ipMatch | IP/CIDR | 192.168.2.0/24 | IP 地址或网段匹配 |
| globMatch | glob 模式 | /alice_data/* | 标准 glob 匹配(比 keyMatch 更通用的通配符) |
具体例子
1 | keyMatch("/alice_data/resource1", "/alice_data/*") → true |
Key Extraction - 键提取函数(返回 string)
不仅判断是否匹配,还提取匹配部分的值
1 | keyGet2("/resource1/action", "/:res/action", "res") → "resource1" |
区别
| 函数 | 对应的匹配函数 | 用途 |
|---|---|---|
| keyGet | keyMatch | 从 ***** 模式中提取匹配的片段 |
| keyGet2 | keyMatch2 | 从 :name 模式中提取指定参数名 |
| keyGet3 | keyMatch3 | 从 {name} 模式中提取指定参数名 |
用途举例:在 matcher 中提取出资源 ID,然后传给其他函数做进一步判断(比如检查资源归属)
自定义函数四步走
当内置函数不够用时,可以注册自己的函数
Step 1:实现实际逻辑
普通的 Go 函数,输入输出类型明确
1 | func KeyMatch(key1 string, key2 string) bool { |
Step 2:包装为 Casbin 签名
Casbin 的表达式引擎要求所有函数统一签名
func(...interface{}) (interface{}, error),所以需要一个适配层
1 | func KeyMatchFunc(args ...interface{}) (interface{}, error) { |
Step 3:注册到 Enforcer
给函数起个名字,注册进去
1 | e.AddFunction("my_func", KeyMatchFunc) |
Step 4:在 Model 中使用
和内置函数用法完全一致
1 | [matchers] |
RBAC
Overview
核心思想
- 传统 ACL(访问控制列表)是用户 → 资源的直接映射,权限多了就爆炸,RBAC 引入角色作为中间层
- 用户 → 角色 → 权限(资源 + 操作)
- 好处:用户变动时只改角色分配,权限变动时只改角色的权限定义,两者解耦
5 种变体
复杂度递增:从最简单的角色继承,到多租户、条件判断、标准对齐
| 变体 | 解决什么问题 | 典型场景 |
|---|---|---|
| Basic RBAC | 角色继承、用户-角色分配 | 基础的角色层级(admin 继承 user 的权限) |
| RBAC with pattern | 模式匹配灵活分配角色 | 路径通配符、正则匹配资源 |
| RBAC with domains | 多租户隔离 | SaaS 产品中,每个租户有自己的角色体系(A 公司的 admin ≠ B 公司的 admin |
| RBAC with conditions | 条件式权限 | “只有在工作时间才能访问”、”只有在同一部门才能操作” |
| RBAC96 | 对齐 NIST RBAC96 标准模型 | 学术合规/标准化需求 |
关联
- Model Syntax 中
[role_definition]的g = _, _→ 就是这里说的 Basic RBAC - Constraint Definition 中的 sod、roleMax 等 → 是 RBAC 角色管理的约束保障
- Functions 中的 keyMatch 系列 → 用于 RBAC with pattern
- Effector → 判定最终 allow/deny 的最后一步
Basic RBAC
角色定义 — [role_definition]
1 | [role_definition] |
- g 是角色系统,_, _ 表示二元关系(用户 → 角色)
- g2 是第二个独立的角色系统,用于资源角色(资源 → 角色)
- 以只定义 g(用户角色),也可以同时定义 g2(资源也有角色体系)
- 两者完全独立,互不干扰
在策略中使用
1 | p, data2_admin, data2, read # data2_admin 角色可以读 data2 |
在 matcher 中使用
1 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act |
g(r.sub, p.sub) 的含义:请求的 subject(alice)是否直接或间接属于策略中定义的 subject(data2_admin)
问题
先看问题:没有角色时怎么做权限?假设 alice 要读 data2,直接把 alice 写进策略
1 | p, alice, data2, read |
问题:100 个用户都要读 data2?写 100 行。有人离职?找到那行删掉。不可维护
引入角色
把”谁能读 data2”从”人“变成”角色“
1 | p, data2_admin, data2, read ← 定义:data2_admin 这个角色能读 data2 |
- 第一行(p 开头):策略规则,说”data2_admin 能读 data2”
- 第二行(g 开头):分组规则,说”alice 是 data2_admin”
现在加人只需要一行:
1 | g, bob, data2_admin ← bob 也能读了 |
删人也简单,删一行 g 规则即可,策略规则完全不用动。
[role_definition] 是什么?
1 | [role_definition] |
- 这句话是在告诉 Casbin:我的模型里有一个叫 g 的分组系统,每条规则有两个字段
- _, _ 就是”两个参数”的意思(占位符),对应策略文件中 g 开头的行:
1 | g, alice, data2_admin |
g2 是什么?
1 | [role_definition] |
- g 管的是用户和角色的关系(用户属于哪个角色)
- 有些场景中,资源也有层级
1 | 文件系统:/docs/engineering/arch/design.doc |
你想让 “对 /docs 有读权限 → 对其下所有子目录和文件都有读权限”,就可以用 g2 定义资源的继承关系
1 | g2, /docs/engineering, /docs |
g 和 g2 互相独立,各管各的
matcher 中的 g() 是怎么工作的
1 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act |
当请求进来时:
1 | e.Enforce("alice", "data2", "read") |
Casbin 把请求代入 matcher
- r.sub = “alice”,r.obj = “data2”,r.act = “read”
- 遍历所有 p 策略,假设检查到 p, data2_admin, data2, read
matcher 变成
1 | g("alice", "data2_admin") && "data2" == "data2" && "read" == "read" |
g(“alice”, “data2_admin”) 做了什么?
它去查所有 g 规则
1 | g, alice, data2_admin ← 找到了!alice 确实属于 data2_admin |
- 所以 g(“alice”, “data2_admin”) → true
- 三个条件都 true → 最终 allow
如果有继承呢?
假设还有:
1 | g, data2_admin, super_admin ← data2_admin 是 super_admin 的子角色 |
现在请求:
1 | e.Enforce("alice", "some_resource", "some_action") |
策略是:
1 | p, super_admin, some_resource, some_action |
matcher 检查 **g(“alice”, “super_admin”)**:
1 | 查 g 规则: |
传递推导:alice → data2_admin → super_admin ✓
所以 g(“alice”, “super_admin”) → true。这就是”间接属于“的含义
总结
- p 规则定义角色能做什么
- g 规则定义谁属于哪个角色(以及角色之间的继承)
- matcher 中的 g() 函数负责查 g 规则表,判断请求者是否属于策略中的角色
- g2 是可选的第二套独立分组系统,用于资源层级等场景
1 | flowchart TB |
核心流程
| 流程 | 描述 |
|---|---|
| Model 定义规则 | 声明请求参数、策略结构、角色系统、匹配表达式、效果组合 |
| Policy 提供数据 | p 规则定义角色权限,g 规则定义角色分配和继承 |
| Enforce 执行判定 | 遍历策略 → matcher 匹配 → g() 查角色表 → effector 合并结果 |
| g() 支持传递 | 直接匹配或通过继承链间接匹配都返回 true |
三个重要注意事项
Casbin 不做身份验证
- Casbin 只存储和评估用户-角色映射,不验证用户或角色是否存在
- Casbin 只管**”授权”(Authorization)**,不管“认证”(Authentication)
- 用户是否存在、角色是否合法,是业务系统的事
不要重名
- 不要让用户名和角色名相同(如用户 alice 和角色 alice),因为 Casbin 无法区分
- Casbin 中一切都是字符串,用户 alice 和角色 alice 是同一个东西,需要区分时用前缀,如 role_alice
继承是传递的
- 如果 A 有角色 B,B 有角色 C,那么 A 自动拥有 C
- 无层级限制,链条可以无限延伸
1 | g, alice, editor # alice → editor |
Token 名称约定
策略中 subject 通常放在第一个位置、命名为 sub
1 | [policy_definition] |
但可以自定义顺序和名称
1 | [policy_definition] |
这时必须告诉 Casbin subject 在哪一列
1 | e.SetFieldIndex("p", constant.SubjectIndex, 2) // 索引从 0 开始,第 3 列是 2 |
否则 DeleteUser() 等 API 会操作错误的列
角色层级(继承)
Casbin 实现的是 RBAC1 风格的层级继承
- alice → editor → admin → superadmin
- alice 自动继承 editor、admin、superadmin 的所有权限
层级深度限制
- 默认最多 10 层继承
- 这是为了防止循环继承导致无限递归,也可以根据业务需要调整
1 | func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager { |
区分用户和角色
Casbin 中用户和角色都是字符串,没有类型上的区分
扁平 RBAC(无继承)
GetAllSubjects()返回 g 规则左边的(用户)GetAllRoles()返回 g 规则右边的(角色)
有层级继承时
同一个名字可能既是用户又是角色
1 | g, alice, editor # editor 是 alice 的角色 |
- 这里 editor 既是一个角色(对 alice 而言),又是一个用户(对 admin 而言)
- 建议:用命名约定区分,如 role::editor,在业务层判断时检查前缀
显式 vs 隐式角色/权限
alice → editor → admin → superadmin
| API | 返回内容 | 示例 |
|---|---|---|
| GetRolesForUser(“alice”) | 直接分配的角色 | [editor] |
| GetImplicitRolesForUser(“alice”) | 直接 + 继承的所有角色 | [editor, admin, superadmin] |
| GetPermissionsForUser(“editor”) | 直接分配的权限 | editor 自身的策略 |
| GetImplicitPermissionsForUser(“alice”) | 直接 + 继承的所有权限 | alice + editor + admin + superadmin 的策略 |
关键区别
- 显式(不带 Implicit):只看直接分配的那一层
- 隐式(带 Implicit):沿继承链往上收集所有层级
实际业务中通常用 Implicit 版本做权限检查,因为你需要知道用户”最终拥有哪些权限“
总结
Model (model.conf)
1 | [request_definition] |
Policy (policy.csv)
1 | p, admin, data1, read # admin 角色对 data1 的读权限 |
用户、角色、权限三层的映射关系(g 规则 + p 规则)
1 | flowchart LR |
alice 沿继承链获得的所有权限(直接 + 隐式)
1 | flowchart TB |
一次 Enforce 请求的完整判定流程
1 | flowchart TB |
RBAC with Pattern
RBAC + 模式匹配 - 解决的是”角色分配规则太多“的问题
基础 RBAC + 给 g 规则的参数加上通配符/正则能力,让一条分组规则能匹配无限多个实体,从”N 条规则描述 N 个资源“变成”1 条规则描述一类资源“
先看问题
- 假设 alice 能读所有书,用基础 RBAC 你要这样写
- 每多一本书,就多一行 g 规则,无法接受
1 | p, alice, book_group, read |
用模式匹配解决
把每本书的具体路径替换成模式,一行搞定
1 | p, alice, book_group, read |
注册匹配函数后,Casbin 就知道 /book/:id 能匹配 /book/1、/book/2、/book/999 等所有路径
怎么注册?
取决于你用不用 domain(多租户):
| 场景 | API | 作用 |
|---|---|---|
| 无 domain | AddNamedMatchingFunc(“g”, …) | 给 g 的参数注册匹配函数 |
| 有 domain | AddNamedDomainMatchingFunc(“g”, …) | 给 g 的 domain 参数注册匹配函数 |
| 两者都要 | 两个都调用 | 用户-角色和 domain 都支持模式 |
代码示例
1 | e, _ := NewEnforcer("./model.conf", "./policy.csv") |
原理
之前基础 RBAC 中 g(a, b) 是精确匹配
1 | g("alice", "editor") → 查表:g, alice, editor → 完全相等 → true |
注册匹配函数后,g(a, b) 变成模式匹配
1 | g("/book/1", "/book/:id") → 用 KeyMatch2 判断 "/book/1" 是否匹配 "/book/:id" → true |
匹配函数就是之前 Functions 文档中介绍的那些(keyMatch、keyMatch2、regexMatch 等),只是用在了 g 规则的参数上
完整流程对比
基础 RBAC:
1 | p 规则: alice 能读 book_group |
RBAC with Pattern
1 | p 规则: alice 能读 book_group |
总结
Model (model.conf)
1 | [request_definition] |
Policy (policy.csv)
1 | p, reader, book_group, read # reader 角色对 book_group 的读权限 |
注册匹配函数
1 | e, _ := NewEnforcer("model.conf", "policy.csv") |
Matcher 中两个 g() 各管各的
1 | g(r.sub, p.sub) → 用户→角色 → g("alice", "reader") 精确查表 |
请求 Enforce(“alice”, “/book/42”, “read”) 的判定:
1 | 1. 遍历 p 策略 → 命中 p, reader, book_group, read |
完整的权限结构 — 用户 → 角色 → 权限,加上资源分组(绿色)用 :id 模式一条规则覆盖所有书籍
1 | flowchart TB |
alice 请求读 /book/42 的完整判定流程,橙色节点是 KeyMatch2 模式匹配的关键步骤
1 | flowchart TB |
基础 RBAC vs RBAC with Pattern 的对比,N 条 → 1 条
1 | flowchart LR |
RBAC with Domains
核心概念
- 之前的 RBAC 模型中,
g = _, _定义的是全局角色 - 一个用户在整个系统中只有一种角色身份 - 但在多租户/多云场景下,同一个用户在不同租户中可能拥有不同角色
| Key | Value |
|---|---|
| 普通 RBAC | alice → admin(全局唯一角色) |
| 域 RBAC | alice → admin in tenant1, alice → user in tenant2(角色随域变化) |
角色定义:三元组
1 | [role_definition] |
- 第 1 个 _:主体(用户)
- 第 2 个 _:角色
- 第 3 个 _:域(Domain) - 租户、工作空间、组织等
核心区别
g = _, _是二元(用户→角色)g = _, _, _是三元(用户→角色在某域中)
策略示例解析
1 | p, admin, tenant1, data1, read |
逐行解读
| 行 | 类型 | 含义 |
|---|---|---|
| p, admin, tenant1, data1, read | 策略 | admin 角色在 tenant1 域中可以 read data1 |
| p, admin, tenant2, data2, read | 策略 | admin 角色在 tenant2 域中可以 read data2 |
| g, alice, admin, tenant1 | 绑定 | alice 在 tenant1 中是 admin |
| g, alice, user, tenant2 | 绑定 | alice 在 tenant2 中是 user |
Enforce 推演
| 请求 | 结果 | 原因 |
|---|---|---|
| Enforce(“alice”, “tenant1”, “data1”, “read”) | Allow | alice 在 tenant1 是 admin,admin 在 tenant1 可读 data1 |
| Enforce(“alice”, “tenant2”, “data1”, “read”) | Deny | alice 在 tenant2 是 user,不是 admin |
| Enforce(“alice”, “tenant2”, “data2”, “read”) | Deny | alice 在 tenant2 是 user,user 没有任何策略 |
| Enforce(“bob”, “tenant2”, “data2”, “read”) | Deny | bob 没有任何 g 绑定 |
关键:alice 虽然全局是 admin(在 tenant1),但她在 tenant2 只是 user,角色不会跨域传播
Matcher 变化
1 | [matchers] |
对比基础 RBAC 的 matcher
| Key | Value |
|---|---|
| 基础 RBAC | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act |
| 域 RBAC | m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act |
多了两部分:
g(r.sub, p.sub, r.dom)- 带域的角色继承查询,检查用户是否在该域中拥有该角色r.dom == p.dom- 请求的域必须匹配策略的域
自定义域字段名
域不一定要叫 dom,可以是 tenant、workspace、org 等任意名称:
1 | [request_definition] |
- 只是把 dom 换成了 tenant,逻辑完全一样,Casbin 并不关心字段名叫什么,只关心位置和匹配关系
- 如果需要在域字段上使用模式匹配(如 keyMatch),只需在 matcher 中**替换相等判断**
1 | m = g(r.sub, p.sub, r.tenant) && keyMatch(r.tenant, p.tenant) && r.obj == p.obj && r.act == p.act |
这样
p.tenant = "org_*"就能匹配r.tenant = "org_engineering"
域字段位置自定义
默认约定域在第 2 个位置(sub, dom, obj, act,index=1),如果放在其他位置
1 | [policy_definition] |
需要在代码中设置索引:
1 | e.SetFieldIndex("p", constant.DomainIndex, 3) |
否则
GetAllUsersByDomain("domain1")这类域相关 API 会报错,因为 Casbin 不知道域字段在哪
总结
1 | flowchart TB |
1 | sequenceDiagram |
RBAC with Conditions
核心思想
- 之前所有 RBAC 模型中,g 规则一旦写入就永久生效
- Conditional RoleManager 引入了一个关键变化:角色绑定可以附带条件,条件不满足时角色绑定失效
- 最典型的场景:时间限定角色 - 某人在某个时间段内临时担任 admin,过期自动失效
角色定义语法变化
1 | [role_definition] |
对比
| 模型 | 定义 | 含义 |
|---|---|---|
| 基础 RBAC | g = _, _ |
用户→角色,无条件 |
| 域 RBAC | g = _, _, _ |
用户→角色→域,无条件 |
| 条件 RBAC | g = _, _, (_, _) |
用户→角色,附带 2 个条件参数 |
| 条件+域 RBAC | g = _, _, _, (_, _) |
用户→角色→域,附带 2 个条件参数 |
(_, _) 中的每个 _ 代表一个额外的参数位,在 g 规则中以实际值填充(如起止时间),运行时传给条件函数判断
策略解析
1 | g, alice, data2_admin, 0000-01-01 00:00:00, 0000-01-02 00:00:00 |
_表示忽略该参数(不做限制),逐行分析
| g 规则 | 条件参数 | 含义 | 时间匹配结果 |
|---|---|---|---|
| alice → data2_admin | 0000-01-01 ~ 0000-01-02 | 只在这 1 天内有效 | false(已过期) |
| alice → data3_admi | 0000-01-01 ~ 9999-12-30 | 几乎永久有效 | true |
| alice → data4_admi | _, _ | 无条件限制(等同普通 RBAC) | true |
| alice → data5_admi | _ ~ 9999-12-30 | 无开始限制,永不过期 | true |
| alice → data6_admi | _ ~ 0000-01-02 | 无开始限制,但已过期 | false |
| alice → data7_admi | 0000-01-01 ~ _ | 有开始时间,无结束限制 | true |
| alice → data8_admi | 9999-12-30 ~ _ | 开始时间在遥远未来 | false |
Enforce 推演
| 请求 | alice 的角色绑定 | 条件函数 | 结果 |
|---|---|---|---|
| (“alice”, “data1”, “read”) | data1 无需角色(p 直接给 alice) | - | true |
| (“alice”, “data2”, “write”) | alice → data2_admin | 时间已过 → false | false |
| (“alice”, “data3”, “read”) | alice → data3_admin | 永久有效 → true | true |
| (“alice”, “data4”, “write”) | alice → data4_admin | 无条件 → true | true |
| (“alice”, “data5”, “read”) | alice → data5_admin | 永不过期 → true | true |
| (“alice”, “data6”, “write”) | alice → data6_admin | 已过期 → false | false |
| (“alice”, “data7”, “read”) | alice → data7_admin | 无结束限制 → true | true |
| (“alice”, “data8”, “write”) | alice → data8_admin | 未到开始时间 → false | false |
关键点:条件函数作用在 g 规则(角色绑定) 上,不是作用在 p 规则(权限策略) 上,**角色绑定失效 = 该角色完全不生效**
运行机制
1 | flowchart TB |
与 无条件的 data3 对比
1 | flowchart TB |
代码注册方式
注意:每条 g 规则需要单独注册条件函数。也可以用 AddNamedMatchingFunc 为所有 g 规则统一注册(但粒度不同)
1 | // 为每条 g 规则绑定条件函数 |
自定义条件函数
- 签名:
func(args ...string) (bool, error) - 可以自定义任意条件逻辑,不只限于时间:
1 | // 示例:IP 范围条件 |
条件+域的组合
1 | [role_definition] |
策略格式变为 5 列:g, user, role, domain, condArg1, condArg2
1 | g, alice, data2_admin, domain2, 0000-01-01 00:00:00, 0000-01-02 00:00:00 |
注册使用 AddNamedDomainLinkConditionFunc(多了 domain 参数):
1 | e.AddNamedDomainLinkConditionFunc("g", "alice", "data2_admin", "domain2", util.TimeMatchFunc) |
三种匹配函数的区别
| 函数类型 | 作用阶段 | 注册方式 | 控制什么 |
|---|---|---|---|
| MatchingFunc | g 查找时 | AddNamedMatchingFunc | 主体的模式匹配(如 u:* 匹配所有用户) |
| DomainMatchingFunc | g 查找时 | AddNamedDomainMatchingFunc | 域的模式匹配(如 org_* 匹配多个组织) |
| LinkConditionFunc | g 规则判断时 | AddNamedLinkConditionFunc | 角色绑定是否生效(如时间条件) |
三者独立且串联:主体要匹配 → 域要匹配 → 条件要满足,角色绑定才真正生效
1 | flowchart LR |
RBAC vs. RBAC96
RBAC96 是什么
RBAC96 是 NIST(美国国家标准与技术研究院)在 1996 年提出的 RBAC 标准模型家族,是学术界和工业界的 RBAC 基准,它定义了四个层级
1 | flowchart TB |
逐级对比
RBAC0 - 基础三要素
RBAC0 定义了 RBAC 的最小可用集合:用户、角色、权限 及其关系
| 要素 | 说明 |
|---|---|
| Users | 用户 |
| Roles | 角色 |
| Permissions | 权限(操作+对象) |
| UA | User-Role Assignment(用户→角色绑定) |
| PA | Permission-Role Assignment(权限→角色绑定) |
Casbin 完全覆盖:p 规则就是 PA,g 规则就是 UA
RBAC1 — 角色继承
在 RBAC0 基础上加入角色层级(Role Hierarchy):角色可以继承其他角色的权限
1 | admin 继承 editor 继承 viewer |
Casbin 完全覆盖:g 规则天然支持传递性继承
1 | g, alice, senior_editor |
alice → senior_editor → editor → viewer,三级继承,alice 拥有所有权限
RBAC2 - 约束
在 RBAC0 基础上加入约束规则,最典型的是
| 约束类型 | 含义 | 示例 |
|---|---|---|
| 职责分离(SoD) | 同一用户不能同时拥有互斥角色 | 不能既是”出纳”又是”审计” |
| 基数限制 | 一个用户最多/最少拥有几个角色 | 每人最多 3 个角色 |
| 先决条件 | 要获得某角色必须先拥有另一角色 | 要当 admin 必须先是 editor |
Casbin 部分覆盖
| RBAC2 要求 | Casbin 支持情况 |
|---|---|
| 职责分离(静态/动态 SoD) | 可通过 deny-override policy effect 间接实现,或通过 constraint_definition |
| 基数限制(如”每人最多 3 个角色”) | 不支持 - Casbin 没有内置数量约束 |
| 先决条件角色 | 不支持 - 角色分配是扁平的 |
RBAC3 - 完整组合
RBAC3 = RBAC1 + RBAC2,即角色继承 + 约束,Casbin 同样是部分覆盖,受限于 RBAC2 的约束支持
三个关键差异
差异一:用户 vs 角色 - 类型不区分
RBAC96 中 User 和 Role 是不同的类型实体,Casbin 不做区分 - 都是字符串
1 | p, admin, book, read # admin 是角色,作为策略主体 |
- GetAllSubjects() 返回所有策略主体:[admin, alice](混合了用户和角色)
- GetAllRoles() 只返回 g 规则右侧:[admin]
实践建议:用命名约定区分,如 user::alice、role::admin。有些项目正是这样做的:u:123 表示用户,r:5 表示角色
差异二:权限类型自由
RBAC96 定义了 7 种固定权限类型,Casbin 的权限是任意字符串
1 | p, admin, /api/users, GET # act = GET |
配合
regexMatch(r.act, p.act)还能用正则匹配,如 p.act = “(GET|POST)” 同时匹配两种 Method
差异三:域(Domains)- 超越 RBAC96
- RBAC96 没有多租户概念
- Casbin 的
g = _, _, _提供了域级别的角色隔离,这是对 RBAC96 的扩展
Priority Model
在基础 RBAC 中,effect 是
some(where (p.eft == allow))- 只要有任何一条 allow 就放行,但现实中有冲突场景
1 | p, alice, data1, read, allow # alice 可以读 |
alice 同时匹配两条策略,一条 allow、一条 deny,谁说了算?Priority Model 就是来解决这个问题的
隐式优先级(策略顺序)
1 | [policy_effect] |
规则:策略在文件/数据库中的顺序就是优先级,**先出现的优先级最高**
1 | p, alice, data1, write, allow # 第 1 条,优先级最高 → 生效 |
缺点:依赖顺序,维护困难,数据库里排序不稳定就会出问题
显式优先级(数字字段)
在 p 定义中加一个 priority 字段,数字越小优先级越高
1 | [policy_definition] |
完整示例分析:
1 | # 角色级策略(优先级 10) |
逐个请求分析:
| 请求 | 匹配的策略 | 结果 | 原因 |
|---|---|---|---|
| alice, data1, write | p,1,alice,data1,write,allow | allow | 优先级 1 > 10,个人策略赢 |
| bob, data2, read | p,1,bob,data2,read,deny (优先级1) vs p,10,data2_allow_group,data2,read,allow (优先级10) | deny | 个人 deny 优先级更高 |
| bob, data2, write | p,10,data2_allow_group,data2,write,allow (优先级10) | allow | 只有角色 allow,无更高优先级 deny |
注意:AddPolicy / AddPolicies 会按优先级排序插入,但 UpdatePolicy 不会重新排序 - 所以不要通过 Update 修改优先级字段的值
自定义优先级字段名
默认字段名是 priority,如果用其他名字(如 customized_priority),需要手动设置索引
1 | // 必须在 LoadPolicy 之前调用 |
不设置会怎样? Casbin 无法识别哪个字段是优先级,策略按原始顺序匹配,优先级逻辑失效
主体优先级(角色/用户层级)
核心思想:优先级不来自数字字段,而是来自角色树的深度 - 越靠近叶子节点(用户),优先级越高
1 | [policy_effect] |
角色树:
1 | role: root # 自动优先级: 30(最低) |
规则:叶子(用户)> 内部角色(editor)> 管理员角色(admin)> 根角色(root)
策略分析:jane, data1, read → allow(jane 的优先级 > editor > admin > root)
1 | p, root, data1, read, deny # 优先级 30 |
设计直觉
- 上层角色定义的是”默认策略“(如 deny),下层用户/角色可以”覆盖“它
- 这符合企业场景 - 高层规定默认禁止,但可以给特定人员开绿灯
约束
- 角色层级必须是树(不能有环),同一用户的多角色应在同一深度
假设角色树如下
1 | root |
alice 同时属于 editor(depth 2)和 viewer(depth 1)。如果策略是:
1 | p, editor, data1, read, allow # editor 说允许 |
- alice 匹配了两条,一条来自 depth 2(editor),一条来自 depth 1(viewer),主体优先级的规则是越深优先级越高,所以 editor 的 allow 赢
- 但问题在于:alice 通过 viewer 路径是 depth 1,通过 editor 路径是 depth 2,同一个用户在不同路径下深度不同,Casbin 无法确定 alice 的”真实深度“是多少,可能取到错误的优先级
如果多角色在同一深度
1 | root |
alice 通过 editor 和 subscriber 都是 depth 2,深度一致,不存在歧义,同深度平局时按策略顺序打破
三种优先级模式对比
| 模式 | 优先级来源 | 配置方式 | 适用场景 |
|---|---|---|---|
| 隐式优先级 | 策略顺序 | `priority(p.eft) | |
| 显式优先级 | 数字字段 | p = priority, sub, obj, act, eft |
需要精细控制优先级,如个人策略覆盖角色策略 |
| 主体优先级 | 角色树深度 | `subjectPriority(p.eft) |
effect 表达式解读
1 | e = priority(p.eft) || deny |
priority(...)— 按优先级排序所有匹配的策略,取优先级最高的那条的 eft 值|| deny— 如果没有匹配的策略,默认 deny
对比基础 RBAC:
1 | e = some(where (p.eft == allow)) # 基础 RBAC:有 allow 就放行 |
Super Administrator
核心机制
在 matcher 末尾加
|| r.sub == "root"
1 | [matchers] |
matcher 是一个布尔表达式,只要整个表达式为 true 就放行
| 条件 | 结果 |
|---|---|
| 正常策略匹配成功 | true → allow |
| 策略没匹配,但 r.sub == “root” | true → allow |
| 策略没匹配,也不是 root | false → deny |
root 用户完全绕过策略检查,不需要任何 p 规则
与 Priority Model 协同
- 当 root 发起请求时,
|| r.sub == "root"让所有策略的 matcher 都为 true - 相当于所有 p 规则都”匹配“了 - 然后交给 effect 判断
| Effect | root 的结果 | 原因 |
|---|---|---|
| some(where (p.eft == allow)) | allow | 所有策略都匹配,只要有一条 allow 就放行 |
| `priority(p.eft) | deny` |
- Priority Model 下,如果存在一条
p, 1, *, *, deny(高优先级 deny),root 也会被拒绝 || r.sub == "root"在 matcher 里确保了”匹配“,但 effect 取的是优先级最高的策略的 eft,root 并没有获得特殊豁免
1 | flowchart TD |









