第 5 章 菜单与角色授权:从零完成一次授权
本文导航

第 5 章 菜单与角色授权:从零完成一次授权
这一章按后台实际操作顺序走一遍:建角色 → 分配菜单 → 建用户 → 分配角色 → 用普通用户验证。每一步都给出接口,方便脚本化。
授权全流程
① 创建角色
② 查询可分配菜单,拿到菜单 ID
③ 给角色分配菜单(页面 + 按钮)
└── 事件监听器把按钮权限写入 Casbin p 行
④ 创建用户
⑤ 给用户分配角色
└── 事件监听器把用户-角色写入 Casbin g 行
⑥ 普通用户登录,验证菜单与权限
① 创建角色
POST /api/v1/roles
{
"name": "运营",
"code": "operator",
"status": 1,
"remark": "日常运营"
}
创建接口不返回 ID,需要再查列表:
GET /api/v1/roles?page=1&pageSize=20
返回的 list 里有角色 ID(十进制字符串)。
② 查询可分配菜单
GET /api/v1/menus/assignable
返回包含按钮的菜单树。节点 ID 是「逗号路径」,例如:
| 节点 | ID |
|---|---|
| 用户管理页面 | SystemRbac,SystemUser |
| 用户管理下的「创建用户」按钮 | SystemRbac,SystemUser,Create |
| 用户管理下的「分页获取用户」按钮 | SystemRbac,SystemUser,Retrieve |
| 角色管理页面 | SystemRbac,SystemRole |
| 角色管理下的「分配菜单给角色」按钮 | SystemRbac,SystemRole,AssignMenusToRole |
分配菜单时把这些 ID 放进数组。只给页面 ID 不够,按钮 ID 才携带接口权限。
③ 给角色分配菜单
PUT /api/v1/roles/{roleID}/menus
{
"menuIds": [
"SystemRbac,SystemUser",
"SystemRbac,SystemUser,Create",
"SystemRbac,SystemUser,Retrieve",
"SystemRbac,SystemUser,Update",
"SystemRbac,SystemUser,Detail"
]
}
处理过程:
RoleService.AssignMenusToRole把菜单关联保存到admin_role_menu;- 发布
AssignMenuToRole事件; - 监听器用
menuRepo.GetPermissionsByMenuIDs(menuIDs...)收集权限; - 先删除该角色的旧 policy,再写入新 policy。
写入后 casbin_rule 中会出现:
p, <roleID>, admin:user:Create
p, <roleID>, admin:user:Retrieve
p, <roleID>, admin:user:Update
p, <roleID>, admin:user:Detail
注意:分配是整份替换。第二次调用时没带的按钮会被取消授权。
④ 创建用户
POST /api/v1/users
{
"username": "demo",
"nickName": "演示用户",
"password": "P@ssw0rd",
"status": 1,
"sex": 1
}
同样不返回 ID,用列表接口查:
GET /api/v1/users?page=1&pageSize=20&username=demo
⑤ 给用户分配角色
POST /api/v1/users/{userID}/roles
{
"roleIDs": ["<roleID>"]
}
处理过程:
- 保存用户与角色的多对多关联;
- 发布
AssignRoleToUser事件; - 监听器先
RemoveFilteredGroupingPolicy(0, userID)清掉该用户的旧分组; - 再写入新的
g, userID, roleID。
casbin_rule 中出现:
g, <userID>, <roleID>
同样是整份替换:不传的角色会被取消。
⑥ 验证
用 demo 登录:
curl -X POST "http://127.0.0.1:8080/api/v1/users/login" \
-H "Content-Type: application/json" \
-d '{"username":"demo","password":"P@ssw0rd"}'
拿返回的 accessToken 查权限:
curl "http://127.0.0.1:8080/api/v1/users/me/permissions" \
-H "Authorization: Bearer <demo-token>"
期望包含刚分配的权限标识。再访问一个有权限的接口:
curl "http://127.0.0.1:8080/api/v1/users?page=1&pageSize=10" \
-H "Authorization: Bearer <demo-token>"
再访问一个没分配的接口,期望 403:
curl -X DELETE "http://127.0.0.1:8080/api/v1/users/1" \
-H "Authorization: Bearer <demo-token>"
菜单 ID 是怎么拼出来的
MenuRepository.GetMenusWithBtn() 会递归设置:
menu.ID = menu.Name // 根节点
child.ID = menu.ID + "," + child.Name // 子节点
所以:
SystemRbac
SystemRbac,SystemUser
SystemRbac,SystemUser,Create
前端勾选树时拿到的就是这些字符串。它们也是 admin_role_menu.menu_id 里存的值。
只分配页面、不分配按钮会怎样
- 用户能看到页面菜单;
- 页面里的接口调用会 403,因为按钮权限没有写入 Casbin;
- 前端如果做了按钮隐藏,按钮也不会显示。
正确做法是页面 ID 和需要的按钮 ID 一起勾选。
删除角色的注意事项
RoleService.DeleteRole 当前只删除角色记录,不会自动清理:
admin_user_role关联;admin_role_menu关联;casbin_rule里该角色的 policy 和用户分组。
删除前应先解除用户关联、清空角色菜单,或手工清理策略,避免留下悬空权限。
自检
- [ ] 能独立完成「建角色 → 分配菜单 → 建用户 → 分配角色」;
- [ ] 知道菜单 ID 是逗号路径;
- [ ] 知道页面菜单和按钮菜单要一起分配;
- [ ] 知道分配是整份替换;
- [ ] 会用普通用户验证,而不是只看超管。
评论
加载评论中…