Casbin 是一个强大且高效的开源访问控制库,它支持多种访问控制模型,例如 ACL (Access Control List)、RBAC (Role-Based Access Control)、ABAC (Attribute-Based Access Control) 等。Casbin 的设计理念是“授权逻辑与业务逻辑分离”,它将授权策略存储在外部配置中,并通过统一的 API 进行管理和验证。Go 语言版本的 github.com/casbin/casbin/v2 是其最活跃和功能最完善的实现之一。

核心思想:提供一个通用的访问控制框架,通过独立的模型配置 (Model) 和策略数据 (Policy) 来定义和管理应用程序的授权规则,使授权逻辑与核心业务代码解耦,实现高度的灵活性和可维护性。


一、为什么需要 Casbin?传统授权方式的局限性

在构建应用程序时,授权 (Authorization) 是一个不可或缺的安全组件,它决定了谁 (Subject) 可以对什么资源 (Object) 执行什么操作 (Action)。传统的授权方式可能面临以下挑战:

  1. 逻辑分散:授权规则通常硬编码在业务逻辑中,分散在多个地方,导致代码难以维护和审计。
  2. 模型僵化:一旦确定了某种授权模型 (如简单的 ACL),后续要切换到更复杂的模型 (如 RBAC 或 ABAC) 成本很高。
  3. 缺乏统一管理:没有统一的 API 或界面来管理用户的角色、权限或属性。
  4. 可扩展性差:随着业务发展,授权规则变得越来越复杂,硬编码的方式难以适应变化。

Casbin 旨在解决这些问题,提供一个通用、灵活、可扩展的授权解决方案:

  • 模型无关:支持多种访问控制模型,并且可以通过配置轻松切换。
  • 策略与代码分离:授权策略以独立的文本文件 (模型) 和数据 (策略) 形式存在,与业务逻辑解耦。
  • 实时更新:策略可以动态加载和修改,无需重启应用。
  • 高性能:内部优化,支持缓存,适用于高并发场景。
  • 多语言支持:除了 Go,还有 Java, PHP, Node.js, Python, Rust 等多种语言实现。

二、Casbin 的核心概念

Casbin 的核心是它的两个配置文件:模型 (Model)策略 (Policy)

2.1 模型 (Model)

模型文件 (通常是 .conf 格式) 定义了 Casbin 使用的访问控制模型结构。它指定了授权规则的通用框架,包括请求者、资源、操作的定义,以及如何组合这些元素进行匹配。

一个典型的 Casbin 模型文件包含以下六个部分:

  • [request_definition]:请求定义,通常是 r = sub, obj, act (请求者,资源,操作)。
  • [policy_definition]:策略定义,通常是 p = sub, obj, act (策略中的请求者,资源,操作)。
  • [policy_effect]:策略生效原则,定义了多个匹配策略时的决策逻辑 (例如 allowdeny)。
    • e = some(where (p.eft == allow)):只要有一个策略允许,就允许。
    • e = !some(where (p.eft == deny)):只要没有策略拒绝,就允许。
    • e = some(where (p.eft == allow)) && !some(where (p.eft == deny)):有允许策略且没有拒绝策略。
  • [matchers]:匹配器,定义了请求和策略如何进行匹配的逻辑表达式。这是 Casbin 灵活性的关键所在。
    • m = r.sub == p.sub && r.obj == p.obj && r.act == p.act (基本匹配)
  • [rbac_model] (可选):RBAC 模型定义,用于定义角色与用户、角色与权限之间的关系。
    • g = _, _ (表示角色继承或用户-角色关系)
  • [function_definition] (可选):自定义函数定义,允许在匹配器中使用自定义的 Go 函数。

示例:基本 RBAC 模型 (rbac_model.conf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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

说明

  • r = sub, obj, act:一个请求包含主题 (用户/角色)、对象 (资源) 和动作。
  • p = sub, obj, act:一个策略包含主题、对象和动作。
  • g = _, _:定义了一个角色继承关系或用户-角色关系,Casbin 的 g 函数会处理这个关系。
  • e = some(where (p.eft == allow)):只要有一条匹配的允许策略,请求就被允许。
  • m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act:匹配规则是:请求的主体 r.sub 必须在策略的主体 p.sub 的角色组中,并且请求的资源和动作必须与策略的资源和动作完全匹配。

2.2 策略 (Policy)

策略文件 (通常是 .csv 格式,也可以存储在数据库中) 包含了具体的授权规则数据,这些数据会填充到模型定义的框架中。

示例:基本 RBAC 策略 (policy.csv)

1
2
3
4
5
p, admin, /data1, read
p, admin, /data1, write
p, alice, /data2, read

g, bob, admin

说明

  • p, admin, /data1, read:策略 p 允许 admin 角色对 /data1 资源执行 read 操作。
  • g, bob, admin:关系 g 表示 bobadmin 角色的成员。

2.3 适配器 (Adapter)

适配器 (Adapter) 是 Casbin 用来从各种持久化存储中加载和保存策略数据的一种机制。Casbin 提供了多种适配器,支持数据库 (MySQL, PostgreSQL, MongoDB, Redis 等)、文件、内存等多种存储方式。

三、基本使用

3.1 安装 Casbin

1
2
3
4
go get github.com/casbin/casbin/v2
go get github.com/casbin/go-plugin/v2 # 如果需要自定义函数
# 其他适配器根据需要安装,例如 MySQL 适配器:
# go get github.com/casbin/gorm-adapter/v3

3.2 快速入门示例

首先,创建模型文件 rbac_model.conf 和策略文件 policy.csv

rbac_model.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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

1
2
3
4
5
6
p, admin, /data1, read
p, admin, /data1, write
p, alice, /data2, read
p, alice, /data2, write

g, bob, admin

Go 代码 (main.go)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main

import (
"fmt"
"log"

"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/util" // 用于匹配器中的一些实用函数,如 keyMatch
)

func check(e *casbin.Enforcer, sub, obj, act string) {
ok, err := e.Enforce(sub, obj, act)
if err != nil {
log.Fatalf("授权检查出错: %v", err)
}

if ok {
fmt.Printf("%s 可以 %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s 不可以 %s %s\n", sub, act, obj)
}
}

func main() {
// 1. 从文件加载模型和策略,创建 Enforcer
// enforcer, err := casbin.NewEnforcer("rbac_model.conf", "policy.csv")

// 1.1 使用 NewEnforcerWithSyncedAdapter 以支持策略热加载 (当使用数据库适配器时尤其有用)
// 本地文件系统也可以使用 NewEnforcer
enforcer, err := casbin.NewEnforcer("rbac_model.conf", "policy.csv")
if err != nil {
log.Fatalf("创建 Casbin Enforcer 失败: %v", err)
}

// 2. 将一些实用函数注册到 Casbin,以便在匹配器中使用
// 例如,keyMatch 可以在匹配器中进行通配符路径匹配
enforcer.AddFunction("keyMatch", util.KeyMatch)
enforcer.AddFunction("keyMatch2", util.KeyMatch2) // 支持更复杂的通配符模式

// 3. 授权检查
fmt.Println("--- 授权检查开始 ---")

// admin 角色的权限
check(enforcer, "admin", "/data1", "read") // admin 可以 read /data1
check(enforcer, "admin", "/data1", "write") // admin 可以 write /data1
check(enforcer, "admin", "/data2", "read") // admin 不可以 read /data2 (通过策略,admin没有此权限)

// bob (是 admin 角色的成员) 的权限
check(enforcer, "bob", "/data1", "read") // bob 可以 read /data1 (通过 g 关系继承 admin 权限)
check(enforcer, "bob", "/data1", "write") // bob 可以 write /data1
check(enforcer, "bob", "/data2", "read") // bob 不可以 read /data2

// alice 的权限
check(enforcer, "alice", "/data1", "read") // alice 不可以 read /data1
check(enforcer, "alice", "/data2", "read") // alice 可以 read /data2
check(enforcer, "alice", "/data2", "write") // alice 可以 write /data2

// 测试一个不存在的用户
check(enforcer, "charlie", "/data1", "read") // charlie 不可以 read /data1

fmt.Println("--- 授权检查结束 ---")

// 4. 运行时管理策略 (如果使用文件适配器,这些修改不会持久化到 policy.csv)
fmt.Println("\n--- 运行时管理策略 ---")

// 添加策略
// AddPolicy(p.sub, p.obj, p.act)
enforcer.AddPolicy("alice", "/data3", "read")
check(enforcer, "alice", "/data3", "read") // alice 现在可以 read /data3

// 添加角色继承关系
// AddGroupingPolicy(user, role)
enforcer.AddGroupingPolicy("charlie", "alice") // charlie 成为 alice 角色的成员
check(enforcer, "charlie", "/data2", "read") // charlie 现在可以 read /data2 (继承 alice 权限)

// 移除策略
// RemovePolicy(p.sub, p.obj, p.act)
enforcer.RemovePolicy("admin", "/data1", "write")
check(enforcer, "admin", "/data1", "write") // admin 不可以 write /data1 了

// 移除角色继承关系
enforcer.RemoveGroupingPolicy("bob", "admin")
check(enforcer, "bob", "/data1", "read") // bob 不可以 read /data1 了

fmt.Println("--- 运行时管理策略结束 ---")

// 持久化策略 (仅当使用支持持久化的适配器时有效,如数据库适配器)
// enforcer.SavePolicy()
}

四、Casbin 访问控制模型详解

Casbin 的强大之处在于其灵活的模型配置。通过修改 rbac_model.conf,你可以实现多种访问控制模型。

4.1 ACL (Access Control List - 访问控制列表)

这是最基础的模型,直接指定用户对资源的权限。

模型 (acl_model.conf)

1
2
3
4
5
6
7
8
9
10
11
[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_policy.csv)

1
2
p, alice, /data1, read
p, bob, /data2, write

4.2 RBAC (Role-Based Access Control - 基于角色的访问控制)

通过引入角色层,将权限赋予角色,再将用户赋予角色。这是最常用的模型之一。

模型 (rbac_model.conf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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_policy.csv)

1
2
3
4
5
6
p, admin, /data1, read
p, admin, /data1, write
p, viewer, /data2, read

g, alice, admin # alice 是 admin 角色
g, bob, viewer # bob 是 viewer 角色

这里 g(r.sub, p.sub) 是 Casbin 内置函数,用于检查 r.sub 是否属于 p.sub 角色组。

4.3 ABAC (Attribute-Based Access Control - 基于属性的访问控制)

根据用户、资源、操作的属性以及环境属性来动态决定访问权限。这是最灵活但也最复杂的模型。

模型 (abac_model.conf)

1
2
3
4
5
6
7
8
9
10
11
12
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub_age, obj_owner, act

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

[matchers]
# 匹配器中直接引用请求的属性和策略的属性
m = r.sub.age > 18 && r.obj.owner == r.sub.name && r.act == p.act

策略 (abac_policy.csv)

1
2
# 这里 p.sub_age 和 p.obj_owner 都是数字或字符串,用于比较
p, 18, bob, read # 策略允许年龄 > 18 且 obj_owner 是 bob 的用户 read

在 Go 代码中,Enforce 时需要传递结构体或 map 来表示属性:
e.Enforce(map[string]interface{}{"age": 20, "name": "bob"}, map[string]interface{}{"owner": "bob"}, "read")

4.4 RESTful 风格的路径匹配

利用 Casbin 的 keyMatchkeyMatch2 函数可以在匹配器中实现 URL 路径的通配符匹配。

模型 (restful_model.conf)

1
2
3
4
5
6
7
8
9
10
11
[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) && r.act == p.act

策略 (restful_policy.csv)

1
2
p, alice, /users/:id, GET # 允许 alice GET /users/1, /users/2 等
p, admin, /admin/*, * # 允许 admin 对 /admin/ 下的所有资源执行所有操作

五、Casbin 的架构图

说明

  • Enforcer:Casbin 的核心执行器,是与应用程序交互的主要接口。它加载模型和策略,并进行授权检查。
  • Model (模型):定义了访问控制规则的结构。
  • Policy (策略):包含具体的访问控制数据。
  • Adapter (适配器):负责 Enforcer 与持久化存储之间的数据交换。
  • Matcher (匹配器):根据模型中定义的逻辑表达式,将授权请求与策略进行匹配,并结合策略生效原则 (Policy Effect) 决定最终的授权结果。

六、高级特性与最佳实践

  1. 数据库适配器:生产环境强烈建议使用数据库适配器 (如 gorm-adapter, xorm-adapter) 来存储策略,以便动态更新和管理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import (
    "github.com/casbin/gorm-adapter/v3" // GORM 适配器
    _ "github.com/go-sql-driver/mysql" // MySQL 驱动
    )

    // ...
    // 初始化数据库适配器
    a, err := gormadapter.NewAdapter("mysql", "user:password@tcp(127.0.0.1:3306)/casbin?charset=utf8&parseTime=True&loc=Local", true)
    if err != nil {
    log.Fatalf("创建数据库适配器失败: %v", err)
    }

    // 从数据库加载模型和策略
    e, err := casbin.NewEnforcer("rbac_model.conf", a)
    if err != nil {
    log.Fatalf("创建 Casbin Enforcer 失败: %v", err)
    }
    e.LoadPolicy() // 从数据库加载策略
    // ...
    // 运行时修改策略后,记得调用 e.SavePolicy() 来持久化
    // e.SavePolicy()
  2. Watcher (观察者):当策略在多个 Casbin 实例之间共享时 (例如在分布式系统中),Watcher 可以通知所有实例策略发生了变化,促使它们重新加载策略,实现策略的热更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import (
    "github.com/casbin/casbin/v2/persist/watcher" // Redis Watcher
    )

    // ...
    w, err := watcher.NewRedisWatcher("redis://localhost:6379/0") // 例如 Redis Watcher
    if err != nil {
    log.Fatalf("创建 Watcher 失败: %v", err)
    }
    e.SetWatcher(w)

    // 当策略发生变化时,调用 e.SavePolicy() 会通过 Watcher 通知其他实例
    // w.Update() 也可以手动触发通知
  3. 日志记录 (Logger):Casbin 支持自定义日志记录,方便调试和审计。

    1
    2
    3
    4
    5
    6
    7
    8
    import (
    "github.com/casbin/casbin/v2/log"
    )

    // ...
    // 启用 Casbin 内部日志
    log.Set "); // Example: log.SetLogger(&myCustomLogger{})
    e.EnableLog(true)
  4. 性能优化

    • 启用缓存e.EnableAutoSave(false)e.EnableAutoLoad(false),然后手动控制 LoadPolicy()SavePolicy()
    • 批处理查询:使用 e.BatchEnforce() 进行批量授权检查。
    • 最小化策略:只存储必要的策略数据。
  5. 自定义函数:在匹配器中可以使用自定义的 Go 函数来处理更复杂的逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func hasPermission(user string, permission string) bool {
    // ... 自定义权限逻辑
    return true
    }

    // 在 Go 代码中注册
    e.AddFunction("hasPermission", hasPermission)

    // 在模型中使用
    // [matchers]
    // m = hasPermission(r.sub, p.sub_permission) && r.obj == p.obj && r.act == p.act
  6. 安全考虑

    • 保护模型和策略文件:确保它们不被未经授权的人修改。
    • 最小权限原则:只授予用户或角色所需的最小权限。
    • 定期审计:定期审查授权策略,确保其仍然符合业务需求。

七、总结

Casbin 是一个功能强大、高度灵活的 Go 语言授权库。它通过模型与策略分离的设计理念,将授权逻辑从业务代码中解耦,使得应用程序的授权管理变得更加清晰、可维护和可扩展。无论是简单的 ACL、复杂的 RBAC 还是动态的 ABAC,Casbin 都能通过配置模型文件轻松实现。结合其丰富的适配器、Watcher、日志和性能优化选项,Casbin 成为了 Go 应用程序中实现企业级访问控制的理想选择。开发者应深入理解其核心概念,并结合最佳实践来构建健壮、安全的授权系统。