策略管理 API
本章是策略增删改查的完整工具书。Casbin 的策略 API 分两层:Management API(面向策略行的底层 CRUD)和 RBAC API(面向用户/角色的高层封装,见 03-RBAC与多租户)。两层操作的是同一份数据。
读接口
| API | 返回 | 说明 |
|---|---|---|
GetPolicy() |
([][]string, error) |
所有 p 策略 |
GetNamedPolicy("p2") |
指定名字的策略(多 policy 定义时) | |
GetFilteredPolicy(fieldIndex, values...) |
按字段过滤 | |
GetGroupingPolicy() |
所有 g 策略 | |
GetFilteredGroupingPolicy(fieldIndex, values...) |
过滤 g 策略 | |
HasPolicy(params...) |
(bool, error) |
某条 p 是否存在 |
HasGroupingPolicy(params...) |
某条 g 是否存在 | |
GetAllSubjects() / GetAllObjects() / GetAllActions() |
p 策略中出现过的去重值 | |
GetAllRoles() |
g 策略中出现过的角色 |
policies, _ := e.GetPolicy()
// [["admin" "/api/*" "GET"] ["editor" "/api/articles/*" "POST"]]
// GetFilteredPolicy:fieldIndex 是起始字段下标,values 依次匹配后续字段
// 查 sub == "admin" 的所有策略(下标 0 = sub)
adminPolicies, _ := e.GetFilteredPolicy(0, "admin")
// 查 obj == "/api/users" 且 act == "GET"(下标 1 = obj,连带匹配下标 2)
p2, _ := e.GetFilteredPolicy(1, "/api/users", "GET")
// 空字符串表示"该位置不过滤":查 act == "GET" 的策略
p3, _ := e.GetFilteredPolicy(0, "", "", "GET")🚨 陷阱:GetFilteredPolicy 是精确匹配,不做 keyMatch。策略里存的是 /api/*,用 /api/users 过滤是查不到的——过滤的是策略原文,不是"哪些策略能匹配该请求"。后者请用 GetImplicitPermissionsForUser 或自己遍历。
写接口
添加
| API | 说明 |
|---|---|
AddPolicy(params...) |
加一条 p;已存在返回 (false, nil) |
AddPolicies(rules [][]string) |
批量添加,原子性:有一条已存在则全部不加 |
AddNamedPolicy("p2", params...) |
向指定策略定义添加 |
AddGroupingPolicy(params...) |
加一条 g(等价于 AddRoleForUser) |
AddGroupingPolicies(rules) |
批量加 g |
ok, err := e.AddPolicy("editor", "/api/articles/*", "POST")
// ok == false 且 err == nil:策略已存在(不是错误)
rules := [][]string{
{"viewer", "/api/articles/*", "GET"},
{"viewer", "/api/comments/*", "GET"},
}
ok, err = e.AddPolicies(rules) // 原子批量:任一已存在则整批失败返回 false删除
| API | 说明 |
|---|---|
RemovePolicy(params...) |
删除精确匹配的一条 p |
RemovePolicies(rules) |
批量删除,原子性同 AddPolicies |
RemoveFilteredPolicy(fieldIndex, values...) |
按过滤条件批量删除 |
RemoveGroupingPolicy(params...) |
删一条 g |
RemoveFilteredGroupingPolicy(fieldIndex, values...) |
过滤删 g |
e.RemovePolicy("editor", "/api/articles/*", "POST")
// 删除 admin 的所有策略(0 = sub 字段)
e.RemoveFilteredPolicy(0, "admin")
// 删除所有针对 /api/legacy/* 的策略(1 = obj 字段)
e.RemoveFilteredPolicy(1, "/api/legacy/*")更新
| API | 说明 |
|---|---|
UpdatePolicy(old, new []string) |
原子替换一条策略 |
UpdatePolicies(olds, news [][]string) |
批量替换 |
UpdateFilteredPolicies(news, fieldIndex, values...) |
把过滤命中的策略整体替换为 news |
UpdateGroupingPolicy(old, new) |
替换 g 行 |
// 把 editor 的 POST 权限改为 POST|PUT
e.UpdatePolicy(
[]string{"editor", "/api/articles/*", "POST"},
[]string{"editor", "/api/articles/*", "(POST)|(PUT)"},
)💡 最佳实践:优先用 UpdatePolicy 而不是 Remove+Add 两步——前者在支持的 adapter 上是单次原子操作,后者中间态可能被并发请求观察到(短暂无权限)。
加载与保存
| API | 方向 | 说明 |
|---|---|---|
LoadPolicy() |
存储 → 内存 | 清空内存后从 adapter 全量重载 |
SavePolicy() |
内存 → 存储 | 清空存储后把内存全量写回 |
LoadFilteredPolicy(filter) |
存储 → 内存 | 只加载子集(需 FilteredAdapter,见 05-适配器与持久化) |
EnableAutoSave(true/false) |
— | 开启后每次 Add/Remove 单条同步写 adapter |
ClearPolicy() |
— | 清空内存策略(不动存储) |
🔬 深入原理:AutoSave 与两种写模式
Enforcer 的写 API 同时维护两份数据:内存(判定用)与 adapter(持久化)。
AutoSave 开(默认,且 adapter 支持时):
AddPolicy ──→ 内存 + adapter 增量写一条 ← 推荐
AutoSave 关:
AddPolicy ──→ 只写内存
SavePolicy ──→ DELETE 全表 + INSERT 全部 ← 大策略集下是灾难SavePolicy 的实现是"删全表再插入全部",十万条策略时既慢又在中间态丢保护。生产环境应依赖 AutoSave 增量写,SavePolicy 只用于初始化导入。
e.EnableAutoSave(true) // gorm-adapter 等数据库 adapter 默认即开启
e.AddPolicy("a", "b", "read") // 内存 + 数据库各写一条,无需 SavePolicy🚨 陷阱:文件 adapter 不支持 AutoSave(不实现增量接口),Add/Remove 只改内存,进程重启即丢。用 CSV 文件做存储时必须手动 SavePolicy(),或者干脆升级到数据库 adapter。
SelfAddPolicy / 通知语义(进阶)
带 Self 前缀的 API(如 SelfAddPolicy)只更新本地(内存+adapter),不触发 Watcher 通知;普通 API 在设置了 Watcher 时会自动广播变更。实现自定义同步逻辑或 Dispatcher 时才会用到,日常忽略即可(Watcher 详见 08-分布式与高性能)。
策略变更的典型封装
管理后台里常见的"角色权限编辑"保存逻辑:
// 把角色 role 的权限整体替换为 perms(全删全增,但只针对该角色)
func SaveRolePerms(e *casbin.SyncedEnforcer, role string, perms [][]string) error {
// 1. 删除该角色现有权限(内存 + DB 各删一次,AutoSave 增量)
if _, err := e.RemoveFilteredPolicy(0, role); err != nil {
return err
}
// 2. 批量写入新权限
rules := make([][]string, 0, len(perms))
for _, perm := range perms {
rules = append(rules, append([]string{role}, perm...))
}
if len(rules) == 0 {
return nil
}
_, err := e.AddPolicies(rules)
return err
}💡 需要严格原子性(删与增之间不能有空窗)时,用支持事务的 adapter 把两步包进一个事务(见 05-适配器与持久化 的事务一节)。
API 速查总表
读: GetPolicy / GetFilteredPolicy / HasPolicy / GetAllSubjects...
增: AddPolicy / AddPolicies / AddGroupingPolicy
删: RemovePolicy / RemoveFilteredPolicy / RemoveGroupingPolicy
改: UpdatePolicy / UpdatePolicies / UpdateFilteredPolicies
载: LoadPolicy / LoadFilteredPolicy
存: SavePolicy(仅初始化用)+ EnableAutoSave
RBAC: AddRoleForUser / GetImplicitPermissionsForUser...(见 03 章)常见陷阱
🚨 AddPolicy 返回 false 不是错误:(false, nil) 表示策略已存在。把它当 error 处理会让"重复提交"变成报错;反过来,把 (false, err) 当成功则会丢数据。两个返回值都要看。
🚨 AddPolicies 是全有或全无:批量里有一条已存在,整批都不会写入且返回 false。增量同步场景应先 HasPolicy 过滤掉已存在的,或逐条 Add。
🚨 改了内存忘了持久化:AutoSave 关闭(或 adapter 不支持)时,Add/Remove 只在内存生效,重启全丢。上线前确认:数据库 adapter + AutoSave 开启,或每次变更后显式 SavePolicy。
🚨 LoadPolicy 会丢弃未保存的内存修改:它先 ClearPolicy 再全量加载。如果有未持久化的内存策略(AutoSave 关闭时的修改),LoadPolicy 直接抹掉。多实例下收到 Watcher 通知调用 LoadPolicy 是正确姿势——前提是所有写都已持久化。
🚨 fieldIndex 数错位:RemoveFilteredPolicy(1, "admin") 删的是 obj == “admin” 的策略而不是 sub。下标从 0 开始按 policy_definition 字段序数,带 priority/domain 字段的模型尤其容易数错——删错就是批量删除事故,先用 GetFilteredPolicy 相同参数预览要删的内容。