文档目录
glen
· 预计阅读 3 分钟

第 1 章 权限模型总览:RBAC 与 Casbin

本文导航
第 1 章 权限模型总览:RBAC 与 Casbin

第 1 章 权限模型总览:RBAC 与 Casbin

这一章建立整体地图。先记住五个对象和它们的关系,后面的接口与操作都只是在这张图上做增删改。

五个对象

对象 代码 说明
用户 model.User 登录主体,可拥有多个角色
角色 model.Role 权限集合,可拥有多个菜单
菜单 router.Menu 页面菜单或按钮菜单;按钮携带权限标识
路由 router.RouterInfo 运行时注册的接口,带访问级别与权限标识
策略 casbin_rule 表 p 行是角色→权限,g 行是用户→角色

关系:

admin_user ──< admin_user_role >── admin_role
                                      │
                                      └──< admin_role_menu >── 菜单 ID
                                                                  │
                                                                  └── 权限标识
                                                                        │
                                                              casbin_rule (p)
                                                                        │
admin_user ──────────────────────────────── casbin_rule (g) ───────────┘

菜单是权限的载体

菜单有两种:

  • 页面菜单(MenuTypeMenu / MenuTypeDir):控制前端路由与侧栏显示;
  • 按钮菜单(MenuTypeBtn):不显示,只用来承载权限标识。

RouterInfoBuilder.Build() 会为每条 AccessAuthorized 路由自动生成一个按钮菜单,名字就是 Handler 方法名,标题是 Name("创建用户") 里的中文:

{
	Name: "Create",
	Meta: router.Meta{Title: "创建用户"},
	MenuType: router.MenuTypeBtn,
	DependentsPermission: []string{
		"admin:user:Create",
	},
}

所以「给角色分配菜单」实际上是在选「这个角色能做哪些页面和按钮」。分配后,菜单上的 DependentsPermission 会被抽出来写进 Casbin。

Casbin 模型

owl/provider/permission/casbin.go 里写死了模型:

[request_definition]
r = sub, act

[policy_definition]
p = sub, act

[role_definition]
g = _, _

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

[matchers]
m = g(r.sub, p.sub) && r.act == p.act

含义:

  • sub 是用户 ID,act 是权限标识;
  • p, <roleID>, <permission> 表示角色拥有某权限;
  • g, <userID>, <roleID> 表示用户属于某角色;
  • 匹配时,只要用户属于某个拥有该权限的角色,就放行。

数据库里对应 casbin_rule 表:

ptype v0 v1
p 761298100 admin:user:Create
g 761298200 761298100

请求链路

POST /api/v1/users
Authorization: Bearer <token>
        ↓
PermissionCheck 中间件
        ↓
1. 用方法和路径在运行时路由表里找到路由
2. 读取 AccessLevel
3. AccessPublic:直接放行
4. 其余:解析 JWT
5. 超管:直接放行
6. AccessAuthenticated:令牌有效即可
7. AccessAuthorized:enforcer.Enforce(userID, permission)
8. AccessSuperAdmin:非超管拒绝

中间件定义在 owl-admin/app/middleware/permission_check.go,挂载在 /api/v1 分组上。

超管为什么不受限

配置里的超管不是数据库用户。登录时如果用户名命中 conf/app.yaml 的 admin.username,服务会构造一个内置用户:

func NewSuperUser() User {
	return User{
		BaseModel:    db.BaseModel{ID: 19941996},
		Username:     "glen",
		Nickname:     "超级管理员",
		IsSuperAdmin: true,
		Permissions:  []string{"*:*:*"},
		Roles:        []Role{{Name: "superAdmin"}},
	}
}

中间件看到 IsSuperAdmin 就直接 Next(),不查 Casbin;/users/me/permissions 也直接返回 *:*:*。

一个完整例子

假设:

  • 角色 运营(ID 1001);
  • 菜单「用户管理」下勾选了「创建用户」按钮;
  • 用户 demo(ID 2001)属于 运营。

则:

p, 1001, admin:user:Create
g, 2001, 1001

demo 请求 POST /api/v1/users:

  • 路由表找到 admin:user:Create;
  • Enforce("2001", "admin:user:Create") 为 true;
  • 放行。

如果把「创建用户」按钮从角色菜单里取消:

  • 监听器先删除角色 1001 的全部 policy;
  • 再按剩余菜单写入新 policy;
  • 下一次请求 Enforce 为 false,返回 403。

自检

  • [ ] 能画出用户、角色、菜单、路由、Casbin 的关系图;
  • [ ] 能说出按钮菜单的权限标识从哪里来;
  • [ ] 能解释 p 行和 g 行分别代表什么;
  • [ ] 能说明超管为什么不查 Casbin。

评论

加载评论中…

暂无上一篇
下一篇 第 2 章 登录与 JWT:令牌怎么来、怎么用

BitLabs © 2026 · 论坛讨论 · 博客长文 · 文档手册