Casbin简介(无AI版)

简介

casbin是一种用于辅助鉴权的工具,他能够通过编写规定格式的配置文件,自动实现权限匹配引擎。
简单来说,算是一种匹配器。

配置文件

要明白casbin怎么使用,就要先看他的配置文件是怎么回事,只要了解了配置文件的格式内容,明白casbin的使用方式也就是分分钟事情了

model.conf

# 1. 访问请求定义:决定了代码调用 e.Enforce(...) 时传入的参数结构
[request_definition]
r = sub, obj, act
# sub: 主体 (Subject,用户/角色)
# obj: 客体 (Object,资源,如 /api/v1/users)
# act: 操作 (Action,如 GET, POST, read, write)

# 2. 策略定义:决定了策略规则文件中每一行的字段格式
[policy_definition]
p = sub, obj, act

# 3. 角色继承定义(可选,用于 RBAC)
[role_definition]
g = _, _  # 表示谁是谁的角色,例如 g(alice, admin)

# 4. 策略生效效果:如何汇总多条匹配策略的结果
[policy_effect]
e = some(where (p.eft == allow))
# 只要有一条策略匹配且允许,最终结果即为允许

# 5. 匹配器:判定请求 r 是否命中策略 p
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
# 含义:r.sub 属于 p.sub 角色,且访问的资源与操作均与策略一致

这个文件就相当于在里面规定了匹配规则。

  1. 第一个访问请求定义,决定了代码在使用匹配器进行匹配时,传入的参数是什么格式的。
  2. 第二个策略定义,也就是你配置权限的格式应该是什么样子的
  3. 第三个角色继承定义,直白一点,就是给人分配权限的时候,是什么格式的,这里就写成什么样子
  4. 第四个是策略生效效果,里面有函数嵌套,是casbin定义好的函数,这里定义了策略应该怎么生效,配置的权限应该如何判定
  5. 第五个则是匹配器,判断代码传入的参数如何命中配置的规则

你可能会好奇,在第四点中,明明p这个变量没有定义eft这个属性,为什么这里可以直接用?
这是因为casbin会默认给所有没有显示设置eft属性的策略,自行添加上值为allow的eft属性,也就是说默认你的规则中只配置了允许,没有配置拒绝,也就是默认除了允许之外的,全是拒绝。

policy.csv

# 角色权限映射:p, 角色/主体, 资源, 操作
p, admin, /api/v1/users, POST
p, admin, /api/v1/users, GET
p, viewer, /api/v1/users, GET

# 用户角色继承关系:g, 用户, 角色
g, alice, admin
g, bob, viewer

刚才定义了策略的定义格式,以及角色继承格式,这里就是对应的真正定义
当然你完全可以不存放在csv中,可以存放在数据库中,其他文件中,都可以,不过都需要遵循这种格式。

代码使用示例

func main() {
	// 1. 初始化 Enforcer(加载模型配置和策略)
	e, err := casbin.NewEnforcer("model.conf", "policy.csv")
	if err != nil {
		log.Fatalf("初始化失败: %v", err)
	}

	// 2. 鉴权校验 (sub, obj, act)
	sub := "alice"           // 访问者
	obj := "/api/v1/users"   // 资源
	act := "POST"            // 操作

	ok, err := e.Enforce(sub, obj, act)
	if err != nil {
		log.Fatalf("鉴权出错: %v", err)
	}

	if ok {
		fmt.Println("鉴权通过:允许访问")
	} else {
		fmt.Println("鉴权拒绝:禁止访问")
	}

	// 3. 动态增删策略与角色(内存变更,若配合持久化 Adapter 则会同步写库)
	e.AddPolicy("viewer", "/api/v1/logs", "GET")
	e.AddGroupingPolicy("charlie", "viewer")
}

这是一段 go 代码的示例,当然不止是 go 可以用,其他语言也都可以用,不过使用的方式大同小异,我们就按照 go 的代码进行讲解。

在使用 casbin 之前,我们需要定义一个匹配器 enforcer ,代码中直接通过使用加载两个文件的方式,实现了这一点。
紧接着,下面在判断鉴权的时候,则是按照上面说的第一点的格式,将参数传递进去,匹配器即可进行鉴权判断。

使用进阶

当然,这样的鉴权示例可能并不够用。

例如,问题一:现在需要开发一个多租户模型的应用,我总不能每个应用都去定义一个角色,然后每个角色再写一遍权限映射吧?

又或者说,问题二:我就是不想只写允许的规则,我就想在白名单的基础上,添加黑名单,这又该怎么办?

问题一 解决方式

我们只需要引入一个概念:domain 即可

model.conf

[request_definition]
# 请求依然携带 domain(当前请求是哪个租户发起的)
r = sub, dom, obj, act

[policy_definition]
# 核心变化:策略 p 不再包含 domain!
# 定义的是全局统一的角色权限(如:admin 始终可以 POST /users)
p = sub, obj, act

[role_definition]
# 域隔离保留在此处:用户在特定 `domain` 下拥有某个角色
g = _, _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
# 1. g(r.sub, p.sub, r.dom):判断用户 r.sub 是否在当前请求的租户 r.dom 下拥有角色 p.sub
# 2. 校验资源与操作(直接使用全局策略,无需对齐 domain)
m = g(r.sub, p.sub, r.dom) && r.obj == p.obj && r.act == p.act

这样配置的话,在用户注册权限的时候添加上 domain ,就可以限制用户在对应 domain 的权限。

在代码的调用中,同样也需要传入 domain

// 1. Alice 访问 domain1 的用户接口:
//    Alice 在 domain1 是 admin -> 命中全局策略 (admin, /api/v1/users, *) -> 允许
e.Enforce("alice", "domain1", "/api/v1/users", "DELETE") // => true

// 2. Alice 访问 domain2 的用户接口:
//    Alice 在 domain2 只是 viewer -> 全局 viewer 无法访问 users -> 拒绝
e.Enforce("alice", "domain2", "/api/v1/users", "DELETE") // => false

// 3. Alice 访问 domain2 的文章接口:
//    Alice 在 domain2 是 viewer -> 命中全局策略 (viewer, /api/v1/posts, GET) -> 允许
e.Enforce("alice", "domain2", "/api/v1/posts", "GET")    // => true

问题二 解决方式

我们只需要显示定义 eft 即可

model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act, eft  # <--- 这里显式加上 eft

[policy_effect]
# 只要有一条 deny 就拒绝;没有 deny 且至少有一条 allow 才允许(拒绝优先)
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

在定义资源权限的时候,也需要显示写上对应资源是否允许

# 允许 alice 访问所有 data 资源
p, alice, data, read, allow

# 但显式禁止 alice 读取某些特殊数据(优先级更高)
p, alice, data_secret, read, deny
posted @ 2026-10-26 12:21  报废之人  阅读(8)  评论(0)    收藏  举报