引言
在现代应用开发中,权限管理是一个核心但复杂的功能模块。传统的基于数据库表的权限系统虽然直观,但在面对复杂权限场景时往往捉襟见肘。本文将深入介绍 Casbin------一个强大的开源访问控制库,并通过大量实例对比传统方案,展示其独特优势。
什么是 Casbin?
Casbin 是一个功能强大且高效的开源访问控制库,最初为 Golang 开发,现已支持多种编程语言(Java、Python、Node.js、.NET、Rust 等)。它的核心理念是将访问控制模型与业务代码解耦,通过配置文件定义权限逻辑。
核心特性
- 多模型支持:ACL、RBAC、ABAC、RESTful、优先级控制等
- 灵活可扩展:通过配置文件定义模型,无需修改代码
- 高性能:内存中策略匹配,支持批量验证
- 持久化支持:多种数据库适配器(MySQL、PostgreSQL、MongoDB 等)
- 分布式友好:支持策略同步和分布式部署
Casbin 的工作原理
Casbin 基于 PERM 元模型(Policy、Effect、Request、Matchers)抽象访问控制:
- Request(请求定义) :定义访问请求的格式,如
sub, obj, act(谁、什么资源、什么操作) - Policy(策略定义):存储具体的权限规则
- Matchers(匹配器):定义请求与策略的匹配逻辑
- Effect(效果):决定最终是允许还是拒绝访问
详细示例
示例 1:基础 ACL 模型
模型配置文件 (model.conf)
ini
[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)
csv
p, alice, data1, read
p, alice, data2, write
p, bob, data2, read
Go 代码实现
go
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func main() {
// 初始化 enforcer
e, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
log.Fatal(err)
}
// 检查权限
checkPermission(e, "alice", "data1", "read") // ✓ 允许
checkPermission(e, "alice", "data1", "write") // ✗ 拒绝
checkPermission(e, "bob", "data2", "read") // ✓ 允许
checkPermission(e, "bob", "data2", "write") // ✗ 拒绝
}
func checkPermission(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
status := "✗ 拒绝"
if ok {
status = "✓ 允许"
}
fmt.Printf("%s %s 对 %s 执行 %s\n", status, sub, obj, act)
}
示例 2:RBAC 角色权限模型
模型配置文件 (rbac_model.conf)
ini
[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)
csv
p, admin, data1, read
p, admin, data1, write
p, admin, data2, read
p, admin, data2, write
p, member, data1, read
p, member, data2, read
g, alice, admin
g, bob, member
g, charlie, member
Go 代码实现
go
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func main() {
e, err := casbin.NewEnforcer("rbac_model.conf", "rbac_policy.csv")
if err != nil {
log.Fatal(err)
}
// 动态添加角色
e.AddRoleForUser("david", "admin")
// 动态添加权限
e.AddPolicy("editor", "data3", "edit")
e.AddRoleForUser("eve", "editor")
// 查询用户角色
roles, _ := e.GetRolesForUser("alice")
fmt.Printf("Alice 的角色: %v\n", roles) // [admin]
// 查询角色的所有用户
users, _ := e.GetUsersForRole("member")
fmt.Printf("member 角色的用户: %v\n", users) // [bob charlie]
// 权限检查
fmt.Println("\n权限验证:")
checkPermission(e, "alice", "data1", "write") // ✓ 允许 (admin)
checkPermission(e, "bob", "data1", "write") // ✗ 拒绝 (member)
checkPermission(e, "eve", "data3", "edit") // ✓ 允许 (editor)
// 批量检查
requests := [][]interface{}{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"charlie", "data1", "read"},
}
results, _ := e.BatchEnforce(requests)
fmt.Println("\n批量验证结果:")
for i, result := range results {
fmt.Printf("%v -> %v\n", requests[i], result)
}
}
func checkPermission(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
status := "✗ 拒绝"
if ok {
status = "✓ 允许"
}
fmt.Printf("%s %s 对 %s 执行 %s\n", status, sub, obj, act)
}
示例 3:RBAC + 域(多租户)
模型配置文件 (rbac_with_domains_model.conf)
ini
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
策略文件 (rbac_with_domains_policy.csv)
csv
p, admin, tenant1, data1, read
p, admin, tenant1, data1, write
p, admin, tenant2, data2, read
p, member, tenant1, data1, read
g, alice, admin, tenant1
g, alice, member, tenant2
g, bob, admin, tenant2
Go 代码实现
go
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func main() {
e, err := casbin.NewEnforcer("rbac_with_domains_model.conf",
"rbac_with_domains_policy.csv")
if err != nil {
log.Fatal(err)
}
// 为用户在特定租户下添加角色
e.AddRoleForUser("charlie", "admin", "tenant1")
e.AddRoleForUser("david", "member", "tenant2")
// 查询用户在特定租户的角色
roles, _ := e.GetRolesForUser("alice", "tenant1")
fmt.Printf("Alice 在 tenant1 的角色: %v\n", roles)
// 查询用户的所有租户
domains, _ := e.GetDomainsForUser("alice")
fmt.Printf("Alice 所在的租户: %v\n", domains)
// 多租户权限验证
fmt.Println("\n多租户权限验证:")
checkDomainPermission(e, "alice", "tenant1", "data1", "write") // ✓ 允许
checkDomainPermission(e, "alice", "tenant2", "data1", "write") // ✗ 拒绝
checkDomainPermission(e, "bob", "tenant2", "data2", "read") // ✓ 允许
checkDomainPermission(e, "bob", "tenant1", "data1", "read") // ✗ 拒绝
}
func checkDomainPermission(e *casbin.Enforcer, sub, dom, obj, act string) {
ok, _ := e.Enforce(sub, dom, obj, act)
status := "✗ 拒绝"
if ok {
status = "✓ 允许"
}
fmt.Printf("%s %s 在 %s 对 %s 执行 %s\n", status, sub, dom, obj, act)
}
示例 4:RESTful API 权限控制
模型配置文件 (restful_model.conf)
ini
[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) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
策略文件 (restful_policy.csv)
csv
p, admin, /api/*, (GET)|(POST)|(PUT)|(DELETE)
p, developer, /api/users/:id, (GET)|(PUT)
p, developer, /api/projects/*, (GET)|(POST)
p, guest, /api/*/public, GET
g, alice, admin
g, bob, developer
g, charlie, guest
Go 代码实现
go
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func main() {
e, err := casbin.NewEnforcer("restful_model.conf", "restful_policy.csv")
if err != nil {
log.Fatal(err)
}
// RESTful API 权限验证
fmt.Println("RESTful API 权限验证:")
// Admin 可以访问所有接口
checkAPI(e, "alice", "/api/users/123", "GET") // ✓
checkAPI(e, "alice", "/api/users/123", "DELETE") // ✓
checkAPI(e, "alice", "/api/projects/5", "POST") // ✓
// Developer 有限制的访问权限
checkAPI(e, "bob", "/api/users/123", "GET") // ✓
checkAPI(e, "bob", "/api/users/123", "DELETE") // ✗
checkAPI(e, "bob", "/api/projects/5", "POST") // ✓
// Guest 只能访问公共资源
checkAPI(e, "charlie", "/api/users/public", "GET") // ✓
checkAPI(e, "charlie", "/api/projects/public", "GET") // ✓
checkAPI(e, "charlie", "/api/users/123", "GET") // ✗
}
func checkAPI(e *casbin.Enforcer, user, path, method string) {
ok, _ := e.Enforce(user, path, method)
status := "✗"
if ok {
status = "✓"
}
fmt.Printf("%s %s %s %s\n", status, user, method, path)
}
示例 5:ABAC 属性权限控制
模型配置文件 (abac_model.conf)
ini
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act
Go 代码实现
go
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
type User struct {
Name string
Age int
Department string
Level int
}
func main() {
e, err := casbin.NewEnforcer("abac_model.conf")
if err != nil {
log.Fatal(err)
}
// 基于属性的策略
e.AddPolicy("r.sub.Age > 18 && r.sub.Department == 'IT'", "server", "access")
e.AddPolicy("r.sub.Level >= 5", "admin_panel", "access")
e.AddPolicy("r.sub.Department == 'HR'", "employee_data", "read")
users := []User{
{Name: "Alice", Age: 25, Department: "IT", Level: 6},
{Name: "Bob", Age: 17, Department: "IT", Level: 3},
{Name: "Charlie", Age: 30, Department: "HR", Level: 4},
}
fmt.Println("ABAC 权限验证:")
for _, user := range users {
checkABAC(e, user, "server", "access")
checkABAC(e, user, "admin_panel", "access")
checkABAC(e, user, "employee_data", "read")
fmt.Println()
}
}
func checkABAC(e *casbin.Enforcer, user User, obj, act string) {
ok, _ := e.Enforce(user, obj, act)
status := "✗"
if ok {
status = "✓"
}
fmt.Printf("%s %s(Age:%d, Dept:%s, Lvl:%d) %s %s\n",
status, user.Name, user.Age, user.Department,
user.Level, act, obj)
}
示例 6:数据库持久化
使用 MySQL 适配器
go
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
"github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 使用 MySQL 适配器
adapter, err := gormadapter.NewAdapter(
"mysql",
"root:password@tcp(127.0.0.1:3306)/casbin_db",
true, // auto create table
)
if err != nil {
log.Fatal(err)
}
e, err := casbin.NewEnforcer("rbac_model.conf", adapter)
if err != nil {
log.Fatal(err)
}
// 启用自动保存
e.EnableAutoSave(true)
// 添加策略(自动保存到数据库)
e.AddPolicy("alice", "data1", "read")
e.AddPolicy("alice", "data1", "write")
e.AddRoleForUser("alice", "admin")
// 从数据库加载策略
e.LoadPolicy()
// 策略查询
policies := e.GetPolicy()
fmt.Println("所有策略:")
for _, p := range policies {
fmt.Printf(" %v\n", p)
}
// 权限验证
ok, _ := e.Enforce("alice", "data1", "read")
fmt.Printf("\nAlice 能读 data1: %v\n", ok)
// 删除策略(自动从数据库删除)
e.RemovePolicy("alice", "data1", "write")
// 手动保存(虽然启用了自动保存)
e.SavePolicy()
}
示例 7:Web 中间件集成(Gin 框架)
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/casbin/casbin/v2"
)
func main() {
r := gin.Default()
// 初始化 Casbin
enforcer, _ := casbin.NewEnforcer("rbac_model.conf", "rbac_policy.csv")
// Casbin 中间件
r.Use(AuthMiddleware(enforcer))
r.GET("/api/data1", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "data1"})
})
r.POST("/api/data1", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "data1 created"})
})
r.Run(":8080")
}
func AuthMiddleware(enforcer *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
// 从上下文获取用户(实际应用中从 JWT 或 Session 获取)
user := c.GetHeader("X-User")
if user == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "未登录"})
return
}
// 获取请求路径和方法
path := c.Request.URL.Path
method := c.Request.Method
// 权限验证
ok, err := enforcer.Enforce(user, path, method)
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "权限验证失败"})
return
}
if !ok {
c.AbortWithStatusJSON(403, gin.H{"error": "无权限访问"})
return
}
c.Next()
}
}
传统数据库表方案 vs Casbin
传统方案示例
典型的基于数据库表的权限系统通常包含以下表结构:
sql
-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255)
);
-- 角色表
CREATE TABLE roles (
id INT PRIMARY KEY,
name VARCHAR(50),
description VARCHAR(255)
);
-- 权限表
CREATE TABLE permissions (
id INT PRIMARY KEY,
name VARCHAR(50),
resource VARCHAR(100),
action VARCHAR(50)
);
-- 用户角色关联表
CREATE TABLE user_roles (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id)
);
-- 角色权限关联表
CREATE TABLE role_permissions (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id)
);
传统方案的代码实现
go
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
type PermissionChecker struct {
db *sql.DB
}
func (pc *PermissionChecker) CheckPermission(userID int, resource, action string) (bool, error) {
query := `
SELECT COUNT(*) FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
JOIN user_roles ur ON rp.role_id = ur.role_id
WHERE ur.user_id = ?
AND p.resource = ?
AND p.action = ?
`
var count int
err := pc.db.QueryRow(query, userID, resource, action).Scan(&count)
if err != nil {
return false, err
}
return count > 0, nil
}
// 使用示例
func main() {
db, _ := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/authdb")
pc := &PermissionChecker{db: db}
hasPermission, _ := pc.CheckPermission(1, "data1", "read")
fmt.Printf("用户有权限: %v\n", hasPermission)
}
对比分析
| 对比维度 | 传统数据库表方案 | Casbin |
|---|---|---|
| 实现复杂度 | 高 - 需要设计多张表和复杂的 SQL | 低 - 配置文件定义模型 |
| 灵活性 | 低 - 修改权限模型需要改表结构和代码 | 高 - 修改配置文件即可切换模型 |
| 性能 | 中 - 每次都查询数据库,可能需要多表 JOIN | 高 - 策略加载到内存,快速匹配 |
| 扩展性 | 困难 - 支持新特性需要大量改动 | 容易 - 支持自定义函数和匹配器 |
| 多租户支持 | 需要额外设计租户隔离逻辑 | 内置域(Domain)支持 |
| RESTful 支持 | 需要手动实现路径匹配 | 内置 keyMatch、regexMatch 函数 |
| 层级角色 | 需要递归查询或额外表 | 原生支持角色继承 |
| 动态策略 | 每次修改都要操作数据库 | 内存中快速增删,可批量操作 |
| 分布式支持 | 需要自行实现缓存同步 | 提供 Watcher 机制 |
| 学习曲线 | 较平缓 - 基于 SQL 和关系模型 | 稍陡 - 需要理解 PERM 模型 |
具体优势对比
1. 灵活性
传统方案: 从 ACL 升级到 RBAC
sql
-- 需要修改表结构
ALTER TABLE permissions ADD COLUMN role_id INT;
-- 需要修改查询逻辑
-- 原来的代码都要改
Casbin: 只需修改配置文件
ini
# 从 ACL 模型
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
# 改为 RBAC 模型
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
2. 性能对比
传统方案: 每次验证都需要数据库查询
go
// 每次都查询数据库,可能涉及多表 JOIN
hasPermission, _ := pc.CheckPermission(userID, resource, action)
Casbin: 策略在内存中,毫秒级响应
go
// 内存中匹配,支持批量验证
results, _ := e.BatchEnforce(requests) // 批量验证 1000+ 请求
性能测试对比
go
package main
import (
"testing"
"github.com/casbin/casbin/v2"
)
func BenchmarkCasbin(b *testing.B) {
e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
e.Enforce("alice", "data1", "read")
}
}
// 结果:Casbin 每次验证约 0.0001 ms
// 传统方案每次验证约 1-5 ms(取决于数据库负载)
3. 复杂场景处理
场景:RESTful API 通配符权限
传统方案:
go
// 需要手动实现复杂的路径匹配逻辑
func matchPath(pattern, path string) bool {
// 实现 /api/users/:id 匹配 /api/users/123
// 实现 /api/* 匹配所有路径
// 代码复杂,容易出错
return false // 简化示例
}
Casbin:
ini
# 内置函数,开箱即用
[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
4. 多租户隔离
传统方案:
sql
-- 需要在每个表添加 tenant_id
ALTER TABLE permissions ADD COLUMN tenant_id INT;
ALTER TABLE roles ADD COLUMN tenant_id INT;
-- 每个查询都要添加租户条件
SELECT * FROM permissions
WHERE tenant_id = ? AND resource = ? AND action = ?;
Casbin:
ini
# 模型自带域支持
[request_definition]
r = sub, dom, obj, act
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
Casbin 的高级特性
1. 优先级控制
ini
[policy_effect]
e = priority(p.eft) || deny
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
csv
p, alice, data1, read, allow, 1
p, alice, data1, read, deny, 2
2. 自定义函数
go
func customMatch(key1, key2 string) bool {
// 自定义匹配逻辑
return key1 == key2
}
e.AddFunction("customMatch", customMatch)
3. 策略同步(分布式)
go
import "github.com/casbin/redis-watcher/v2"
watcher, _ := rediswatcher.NewWatcher("127.0.0.1:6379")
e.SetWatcher(watcher)
// 当一个节点修改策略时,自动通知其他节点
watcher.SetUpdateCallback(func(string) {
e.LoadPolicy()
})
4. 条件策略
go
// 时间条件
e.AddPolicy("alice", "data1", "read",
"r.sub.Time > '2024-01-01' && r.sub.Time < '2024-12-31'")
// IP 条件
e.AddPolicy("bob", "admin", "access",
"ipMatch(r.sub.IP, '192.168.1.0/24')")
最佳实践
1. 模型选择建议
- 简单应用:使用 ACL
- 企业应用:使用 RBAC
- 多租户 SaaS:使用 RBAC + Domain
- 复杂业务规则:使用 ABAC
- API 网关:使用 RESTful 模型
2. 性能优化
go
// 1. 启用批量验证
requests := [][]interface{}{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
}
results, _ := e.BatchEnforce(requests)
// 2. 使用过滤策略(大数据量场景)
filter := &Filter{
P: []string{"alice"}, // 只加载 alice 相关的策略
}
e.LoadFilteredPolicy(filter)
// 3. 预编译策略(如果策略很少变化)
e.EnableEnforce(true)
3. 安全建议
go
// 1. 始终验证输入
if sub == "" || obj == "" || act == "" {
return false, errors.New("invalid input")
}
// 2. 记录审计日志
e.EnableLog(true)
e.SetLogger(&MyLogger{}) // 自定义日志记录器
// 3. 使用白名单模式
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
4. 测试建议
go
func TestPermissions(t *testing.T) {
e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
tests := []struct {
sub string
obj string
act string
expect bool
}{
{"alice", "data1", "read", true},
{"alice", "data1", "write", false},
{"bob", "data2", "write", true},
}
for _, tt := range tests {
result, _ := e.Enforce(tt.sub, tt.obj, tt.act)
if result != tt.expect {
t.Errorf("Enforce(%s, %s, %s) = %v, want %v",
tt.sub, tt.obj, tt.act, result, tt.expect)
}
}
}
何时使用传统方案?
虽然 Casbin 有诸多优势,但在以下场景下,传统数据库表方案可能更合适:
- 权限模型极其简单:只有简单的用户-角色-权限,且永远不会变复杂
- 团队不熟悉 Casbin:学习成本高于收益
- 已有成熟系统:迁移成本过高
- 需要复杂的权限查询:如"查询某资源的所有授权用户"(虽然 Casbin 也能做)
总结
Casbin 通过配置化、模型化的方式,将权限管理从繁琐的数据库操作和代码逻辑中解放出来。它的核心优势在于:
- 声明式配置:权限模型清晰可见,易于维护
- 高性能:内存匹配,毫秒级响应
- 极强的扩展性:支持多种模型和自定义逻辑
- 开箱即用的高级特性:多租户、RESTful、ABAC 等
- 活跃的社区:多语言支持,丰富的适配器和中间件
相比传统的数据库表方案,Casbin 在灵活性、性能和可维护性上都有明显优势。对于需要复杂权限控制的现代应用,Casbin 无疑是更好的选择。
参考资源
关键词:Casbin, 权限管理, RBAC, ACL, ABAC, 访问控制, 多租户, RESTful, Golang, 权限系统