Skip to content
Go
最佳实践与陷阱

最佳实践与陷阱

本章汇总全手册的陷阱与最佳实践,并给出模型选型速查和上线前检查清单。各条目标注了详细讲解所在章节。

陷阱汇总

模型与匹配

🚨 陷阱 后果 正解 详见
忽略 Enforce 的 error 配置错误被当成"无权限" error 回 500 + 告警,!ok 才是 403 00 / 07
Enforce 参数顺序传错 不报错,永远 false 参数顺序 = request_definition 顺序 01
deny 策略配 allow-override effect deny 完全不生效 deny 必须配 !some(where (p.eft == deny)) 02
== 匹配通配符策略 /api/* 永远匹配不上 用 keyMatch 系列函数 06
/foo 不匹配 /foo/* 列表页 403 授两条或用正则 ^/foo(/.*)?$ 06
regexMatch 不加锚点 DELETE 命中 ELET,方法越权 "^(GET|POST)$" 06
obj 带 query string keyMatch 失败 c.Request.URL.Path 06 / 07
domain 模型漏 r.dom == p.dom 跨租户越权 matcher 双条件 + 负向测试 03
ABAC 字段小写 取不到值 结构体字段必须可导出 02
CSV 含逗号字段不加引号 策略被拆错列 "(GET)|(POST)" 加双引号 00

RBAC

🚨 陷阱 后果 正解 详见
用直接版 API 查继承权限 权限显示不全 GetImplicit* 系列 03
用户名与角色名撞名 意外继承 user: / role: 前缀隔离 03
删角色只删 g 行 幽灵权限残留 DeleteRole / DeleteUser 03
环形继承 行为依赖层级上限 写入前做环检测 03

存储与同步

🚨 陷阱 后果 正解 详见
CSV 文件用于生产 无 AutoSave,重启丢数据 gorm-adapter 05
大策略集调 SavePolicy 删全表重写,慢且有空窗 依赖 AutoSave 增量 04
过滤加载后 SavePolicy 部分覆盖全部,策略丢失 过滤模式只用增量 API 05
绕过 API 直改数据库 内存与库不一致 只通过 Casbin API 改策略 05
多实例无 Watcher 改权限"不生效" redis-watcher + 兜底重载 08
依赖 pub/sub 不丢消息 断连期间策略不同步 定时 LoadPolicy 兜底 08
模型字段变更不迁移数据 v 列整体错位 改模型 = 写数据迁移 05

并发与性能

🚨 陷阱 后果 正解 详见
Web 服务用裸 Enforcer data race SyncedEnforcer 01
每请求 NewEnforcer 每次全量加载策略 进程级单例 07
自定义函数里做 IO 每条策略一次 IO 纯内存计算,数据前置传入 06
逐条导入大批策略 通知风暴 + 重载风暴 AddPolicies 批量 08
eval 策略泛滥 每次判定重编译表达式 控制数量,管住写入口 06

模型选型速查

只有几个用户,权限直挂 ────────────────→ ACL
标准后台(用户-角色-权限)───────────────→ RBAC
SaaS 多租户 ───────────────────────────→ RBAC with domains
保护 REST API ─────────────────────────→ RBAC + keyMatch2 + regexMatch
"只能操作自己的资源" ────────────────────→ ABAC(或 keyGet)
白名单 + 个别封禁 ──────────────────────→ allow-and-deny + eft 字段
规则要运营可配、带条件 ──────────────────→ eval() 动态规则(管住入口)

💡 最佳实践清单

设计

  1. 模型进代码库,策略进数据库:model.conf 用 go:embed 编译进二进制、随版本评审;策略用 gorm-adapter 存库
  2. ID 加命名空间前缀user:1001role:editortenant:42,杜绝撞名
  3. Casbin 只存权限关系:角色显示名、描述等业务字段放业务表,用 key 关联
  4. 两层防线:中间件管 API 面(RBAC),业务层管对象面(ABAC)
  5. 超管写在 matcher 里|| r.sub == "root"),不散落在业务 if 里

编码

  1. Web 服务一律 SyncedEnforcer 单例
  2. Enforce 的两个返回值都处理:err → 500 + 告警;!ok → 403
  3. 改模型必写测试:核心用例(正向 + 越权负向)做成表驱动单测;改 matcher 先在 Casbin Editor 验证
  4. EnforceEx 做审计与排障:记录命中的策略行
  5. 批量操作用批量 APIAddPolicies / BatchEnforce

运维

  1. 多实例 = adapter + Watcher + 定时兜底重载,三件缺一不可
  2. 策略变更要有审计日志:谁、何时、改了哪条
  3. 权限管理接口本身要授权:只有超管角色可调
  4. 上线前做负向测试:普通用户访问管理接口、A 租户访问 B 租户资源,必须 403

表驱动测试模板

func TestAuthz(t *testing.T) {
	e := newTestEnforcer(t) // 加载真实 model + 测试策略

	tests := []struct {
		name          string
		sub, obj, act string
		want          bool
	}{
		{"admin 可删用户", "role:admin", "/api/users/1", "DELETE", true},
		{"editor 可发文章", "user:bob", "/api/articles", "POST", true},
		{"viewer 不可发文章", "user:carol", "/api/articles", "POST", false},   // 负向
		{"跨租户拒绝", "user:alice", "/api/tenant2/data", "GET", false},       // 负向
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := e.Enforce(tt.sub, tt.obj, tt.act)
			if err != nil {
				t.Fatalf("enforce error: %v", err)
			}
			if got != tt.want {
				t.Errorf("Enforce(%s, %s, %s) = %v, want %v",
					tt.sub, tt.obj, tt.act, got, tt.want)
			}
		})
	}
}

生产检查清单

上线前逐项确认:

  • 使用 github.com/casbin/casbin/v3(不是 v1/v2 旧包)
  • Enforcer 为 SyncedEnforcer(或 SyncedCached),进程级单例
  • 策略存储为数据库 adapter,AutoSave 生效(写一条策略后查库确认)
  • 多实例部署已配置 Watcher,并验证:实例 A 改策略,实例 B 秒级生效
  • 有兜底重载(StartAutoLoadPolicy 或重连补偿)
  • Enforce 的 error 走 500 + 告警,不与 403 混淆
  • 公开路由(登录/健康检查)不经过授权中间件
  • 核心权限用例有表驱动单测,含越权负向用例
  • domain 模型通过了跨租户负向测试
  • 权限管理接口自身有授权 + 审计日志
  • 策略列宽足够(长 URL/ID 不被 100 字符截断)
  • 大批量策略导入用批量 API,导入后各实例已同步

参考资源