Go Casbin 学习
Casbin 基础概念
什么是 Casbin
Casbin 是一个授权库,用于处理各种复杂的访问控制场景。
Casbin - 中文文档它支持:
- ACL (访问控制列表)
- RBAC (基于角色的访问控制)
- ABAC (基于属性的访问控制)
核心概念
- 模型(Model) : 定义访问控制策略的抽象表示
- 策略(Policy) : 具体的访问规则
- Enforcer : 执行器,负责加载模型和策略,执行访问控制决策
在 Casbin 的授权流程中:
- [matchers]先执行 :负责筛选出所有相关的策略规则
- [policy_effect]后执行 :负责解释这些匹配到的规则
安装 Casbin
go get github.com/casbin/casbin/v2
最简单的 ACL 示例
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
// 创建enforcer,加载模型和策略文件
e, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
panic(err)
}
// 检查权限
sub := "alice" // 用户
obj := "data1" // 资源
act := "read" // 操作
ok, err := e.Enforce(sub, obj, act)
if err != nil {
panic(err)
}
if ok {
fmt.Println("允许访问")
} else {
fmt.Println("拒绝访问")
}
}
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
ACL 策略文件
p, alice, data1, read
p, bob, data2, write
模型、策略文件配置理解
model.conf 配置
model.conf - 模型定义文件
模型文件定义了访问控制的基本结构和逻辑,它由多个部分组成。
[request_definition] - 请求定义
[request_definition]
r = sub, obj, act
定义了访问请求的格式,通常包含三个元素:
- sub(subject): 访问主体,通常是用户
- obj(object): 访问资源/对象
- act(action): 操作/权限
可以扩展更多字段,如:r = sub, obj, act, time
[policy_definition] - 策略定义
[policy_definition]
p = sub, obj, act
定义了策略的存储格式,与请求定义对应
在 RBAC 中可能还包含角色:p = sub, obj, act 或 p = role, obj, act
[role_definition] - 角色定义
RBAC 专用
[role_definition]
g = _, _
这行配置定义了:
g 是角色关系的名称(可以自定义,如 group)
_,_表示关系的两端:用户, 角色
实际意义
这表示:
- 第一个 _代表用户(或子角色)
- 第二个 _代表角色(或父角色)
例如在策略中:
g, alice, admin
表示:用户 alice 拥有 admin 角色。
[policy_effect] - 策略效果
定义了多个策略规则如何组合生效。
eft 是"effect"的缩写,表示这条策略规则的效果。
不看用户名 :策略效果评估时,不区分是哪个用户的规则。
只看规则存在 :只要存在任何 deny 规则,!some(deny)就会为 false。
常见选项:
- some(where (p.eft == allow)):有一条允许则允许(类似 OR 逻辑)
- !some(where (p.eft == deny)):没有拒绝则允许
- some(where (p.eft == allow)) && !some(where (p.eft == deny))
[policy_effect]
e = some(where (p.eft == allow))
策略效果详解
some(where (p.eft == allow))
含义:
- 检查所有匹配的策略规则
- 只要有一条规则的效果是 allow,就允许访问
- 类似 OR 逻辑(多条允许规则中有一条成立即可)
示例:
p, alice, data1, read, allow
p, alice, data1, read, deny
结果:允许访问(因为有一条 allow 规则)
!some(where (p.eft == deny))
含义:
- 检查所有匹配的策略规则
- 如果没有任何规则的效果是 deny,就允许访问
- 类似"没有反对就通过"的逻辑
示例 1:
p, alice, data1, read, allow
p, bob, data1, read, allow
结果:允许访问(因为没有 deny 规则)
示例 2:
p, alice, data1, read, allow
p, bob, data1, read, allow
p, alice, data1, read, deny
结果:拒绝访问(因为有 deny 规则)
some(where (p.eft == allow)) && !some(where (p.eft == deny))
含义:
- 必须同时满足两个条件:
- 至少有一条规则是 allow
- 没有任何规则是 deny
- 这是最严格的策略效果,常用于需要明确允许且没有拒绝的场景
示例 1:
p, alice, data1, read, allow
p, bob, data1, read, allow
结果:允许访问(有 allow 且没有 deny)
示例 2:
p, alice, data1, read, deny
p, bob, data1, read, deny
结果:拒绝访问(没有 allow)
[matchers]-匹配器
定义了请求如何匹配策略的核心逻辑。
- 可以使用逻辑运算符:&&, ||, !
- 可以使用内置函数或自定义函数
- 在 RBAC 中通常包含角色检查:
- m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
- r.sub == p.sub:请求的主体(subject)必须匹配策略的主体
- r.obj == p.obj:请求的资源(object)必须匹配策略的资源
- r.act == p.act:请求的操作(action)必须匹配策略的操作
policy.csv 配置
策略文件存储了具体的访问控制规则,其格式由模型文件中的[policy_definition]决定。
基本 ACL 策略
对于基本 ACL 模型:
p, alice, data1, read
p, bob, data2, write
每行代表一条策略规则
- 格式:p, sub, obj, act
- p:表示这是策略规则(由[policy_definition]定义)
- 后续字段对应模型定义
RBAC 策略
对于 RBAC 模型,通常有两类策略:
- 权限分配策略:
p, admin, data1, read
p, admin, data1, write
p, user, data1, read
表示角色对资源的权限
- 用户角色分配策略:
g, alice, admin
g, bob, user
- g 表示角色分配(由[role_definition]定义)
- 表示用户拥有的角色
策略文件中的特殊语法
通配符 :可以使用*匹配任意值
p, admin, *, *
否定:可以在 eft 字段指定 allow/deny
p, alice, data1, read, deny
优先级:可以使用优先级数字
p, admin, data1, read, allow, 1
配置示例解析
ACL 示例
model.conf
[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
policy.csv
p, alice, data1, read
p, bob, data2, write
逻辑解释 :
- 当请求(alice, data1, read)时:
- 匹配第一条策略 p, alice, data1, read
- r.sub == p.sub(alice == alice)
- r.obj == p.obj(data1 == data1)
- r.act == p.act(read == read)
- 匹配成功,允许访问
- 当请求(alice, data2, read)时:
- 没有匹配的策略
- 拒绝访问
RBAC 示例
model.conf
[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
policy.csv
p, admin, data1, read
p, admin, data1, write
p, user, data1, read
g, alice, admin
g, bob, user
逻辑解释 :
- 当请求(alice, data1, read)时:
- g(r.sub, p.sub)检查 alice 的角色
- alice 是 admin(通过 g, alice, admin)
- 查找 admin 的权限:
- p, admin, data1, read 匹配
- 允许访问
- 当请求(bob, data1, write)时:
- bob 是 user 角色
- user 角色只有 data1 的 read 权限
- 没有匹配的策略
- 拒绝访问
理解模型和策略的关系
- 模型(model.conf) :相当于"宪法",定义了整个访问控制系统的规则框架
- 定义了"如何判断"权限
- 定义了策略的存储格式
- 定义了角色系统的工作方式
- 策略(policy.csv) :相当于"法律条文",是具体的规则实例
- 存储了谁(用户/角色)对什么资源有什么权限
- 存储了用户-角色映射关系
- 可以根据模型定义的格式灵活扩展
RBAC 示例
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
e, err := casbin.NewEnforcer("rbac_model.conf", "rbac_policy.csv")
if err != nil {
panic(err)
}
// 检查用户角色
roles, err := e.GetRolesForUser("alice")
if err != nil {
panic(err)
}
fmt.Println("Alice的角色:", roles)
// 检查权限
sub := "alice"
obj := "data2"
act := "read"
ok, err := e.Enforce(sub, obj, act)
if err != nil {
panic(err)
}
if ok {
fmt.Println("允许访问")
} else {
fmt.Println("拒绝访问")
}
}
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
RBAC 策略文件
p, admin, data1, read
p, admin, data1, write
p, admin, data2, read
p, admin, data2, write
p, user, data1, read
g, alice, admin
g, bob, user
使用数据库存储策略
实际项目中,我们通常将策略存储在数据库中:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/gorm-adapter/v3"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 初始化数据库连接
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 创建适配器
adapter, err := gormadapter.NewAdapterByDB(db)
if err != nil {
panic(err)
}
// 创建enforcer
e, err := casbin.NewEnforcer("rbac_model.conf", adapter)
if err != nil {
panic(err)
}
// 添加策略
e.AddPolicy("admin", "data1", "read")
e.AddPolicy("admin", "data1", "write")
e.AddRoleForUser("alice", "admin")
// 保存策略
err = e.SavePolicy()
if err != nil {
panic(err)
}
// 检查权限
ok, err := e.Enforce("alice", "data1", "read")
if err != nil {
panic(err)
}
fmt.Println("允许访问:", ok)
}
高级功能
ABAC (基于属性的访问控制)
Casbin 支持 ABAC,可以根据对象的属性进行访问控制:
type Book struct {
Name string
Author string
}
func main() {
e, err := casbin.NewEnforcer("abac_model.conf")
if err != nil {
panic(err)
}
book := Book{Name: "Casbin指南", Author: "alice"}
// 检查权限
ok, err := e.Enforce("alice", book, "read")
if err != nil {
panic(err)
}
fmt.Println("允许访问:", ok)
}
ABAC 模型文件 (abac_model.conf):
[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.Author && r.act == p.act
使用函数进行复杂匹配
可以在模型中使用自定义函数:
e, err := casbin.NewEnforcer("model_with_func.conf")
if err != nil {
panic(err)
}
// 添加自定义函数
e.AddFunction("timeMatch", func(args ...interface{}) (interface{}, error) {
// 实现时间匹配逻辑
return true, nil
})
然后在模型中使用:
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act && timeMatch(r.time)
调试技巧
使用 Enforcer 的打印方法查看加载的模型和策略 :
fmt.Println(e.GetModel())
fmt.Println(e.GetPolicy())
检查角色映射关系 :
roles, _ := e.GetRolesForUser("alice")
fmt.Println(roles)
启用日志 :
e.EnableLog(true)