权限管理,大多是后端处理的功能,涉及到用户,角色,菜单和部门等。
前端权限
根据 get:/role/dynamic 这个接口获取到当前用户角色所对应的菜单权限,动态生成路由信息,然后通过vue-router的 addRoute 动态配置路由
ts
// src/router/generator-routers.ts
export function createRouterGuards(router: Router) {
router.beforeEach(async (to, from, next) => {
// ...
await userStore.GetConfig();
const routes = await asyncRouteStore.generateRoutes(userInfo);
// 动态添加可访问路由表
routes.forEach((item) => {
router.addRoute(item as unknown as RouteRecordRaw);
});
}
}
作者在这里,将请求配置权限菜单的逻辑,放在守卫里,感觉不合理,因为每次路由变化,都会进入这个逻辑,虽然加了判断,已经添加了路由的话,不走下面的加载逻辑,但仍觉的不妥。
ts
if (asyncRouteStore.getIsDynamicAddedRoute) {
next();
return;
}
以上是跟菜单有关的权限,除此之外,还有对数据也有限制
角色列表中,非超管用户,只能看到自己的角色以及下级的角色,后端在role.go中处理
go
// server/internal/logic/admin/role.go
// List 获取列表
func (s *sAdminRole) List(ctx context.Context, in *adminin.RoleListInp) (res *adminin.RoleListModel, totalCount int, err error) {
// ...
// 非超管只获取下级角色
if !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
pid = contexts.GetRoleId(ctx)
mod = mod.WhereLike(dao.AdminRole.Columns().Tree, "%"+tree.GetIdLabel(pid)+"%")
}
// ...
}
这里用到了判断是否是超管,超管的信息是在server/internal/global/init.go中的Init函数中加载的
go
func Init(ctx context.Context) {
// ...
// 加载超管数据
service.AdminMember().LoadSuperAdmin(ctx)
// ...
}
这个Init函数会在main函数中被调用,注意这个Init函数,不是go包中默认运行时加载的init函数。
角色权限
该项目使用了RBAC权限管理模型,外加 casbin 权限框架辅助。从数据库中可以看到有关权限的几张表
- hg_admin_member
- hg_admin_role
- hg_admin_menu
- hg_admin_role_menu
- hg_admin_member_role
- hg_admin_role_casbin
其中hg_admin_role_casbin里存放的时候,角色对应的具体权限,这些数据会被应用启动时加载,缓存在内存中。另外,在权限被修改时,系统会重新修改 casbin 权限内容。
go
cmd.Main.Run(ctx)
这里运行,Main 的Func函数,初始化一些内容,包括读取权限信息到casbin。
go
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
//...
// 初始化casbin权限
casbin.InitEnforcer(ctx)
//...
}
role权限修改时,作者将casbin权限清空,重新加载
go
// UpdatePermissions 更改角色菜单权限
func (s *sAdminRole) UpdatePermissions(ctx context.Context, in *adminin.UpdatePermissionsInp) (err error) {
// ...
return casbin.Refresh(ctx)
}
作者在 server/internal/library/casbin/enforcer.go 的 clear 中,是将全部权限都清空掉,重新加载,这里浪费了一些资源,个人觉得,可以通过 group 清理和重新加载对应role的权限,这样性能会好一些。
上面讲了一大堆权限,具体应用是在这个中间件文件中, 这个中间件会在设置路由的时候配置。
go
// server/internal/logic/middleware/admin_auth.go
// 验证路由访问权限
if !service.AdminRole().Verify(ctx, path, r.Method) {
g.Log().Debugf(ctx, "AdminAuth fail path:%+v, GetRoleKey:%+v, r.Method:%+v", path, contexts.GetRoleKey(ctx), r.Method)
response.JsonExit(r, gcode.CodeSecurityReason.Code(), "你没有访问权限!")
return
}
设置权限中间件
go
// server/internal/router/admin.go
group.Group(simple.RouterPrefix(ctx, consts.AppAdmin), func(group *ghttp.RouterGroup) {
group.Bind(
common.Site, // 基础
)
group.Middleware(service.Middleware().AdminAuth)
group.Bind(
common.Console, // 控制台
// ...
)
}
数据权限
在前端,权限管理-角色管理中,可以对每个角色这是数据权限,分为按全部权限、按部门权限,按上下级权限等方式划分,设置是比较简单的,只是将对应的id关联到role上即可,具体获取数据的时候,略微复杂一些。在需要进行权限划分的数据获取时,使用 FilterAuthWithField 函数进行过滤数据,过滤的字段不是固定的,可以是created_by, 也可以是member_id。
go
// FilterAuthWithField 过滤数据权限,设置指定字段
func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
return func(m *gdb.Model) *gdb.Model {
// ...
switch role.DataScope {
case consts.RoleDataAll: // 全部权限
// ...
case consts.RoleDataNowDept: // 当前部门
m = m.WhereIn(filterField, getDeptIds(co.User.DeptId))
case consts.RoleDataDeptAndSub: // 当前部门及以下部门ds
m = m.WhereIn(filterField, getDeptIds(GetDeptAndSub(ctx, co.User.DeptId)))
case consts.RoleDataDeptCustom: // 自定义部门
m = m.WhereIn(filterField, getDeptIds(role.CustomDept.Var().Ints()))
case consts.RoleDataSelf: // 仅自己
m = m.Where(filterField, co.User.Id)
case consts.RoleDataSelfAndSub: // 自己和直属下级
m = m.WhereIn(filterField, GetSelfAndSub(ctx, co.User.Id))
case consts.RoleDataSelfAndAllSub: // 自己和全部下级
m = m.WhereIn(filterField, GetSelfAndAllSub(ctx, co.User.Id))
default:
g.Log().Panic(ctx, "dataScope is not registered")
}
return m
}
}
这是一个数据库中间件,gorm中也有相应的功能,会根据不同的数据权限,生成不同的sql where条件,最终查询出不同的数据。其中,根据上下级设置数据权限,具体是通过创建账号关联的,用户创建的账号,就是该用户的下级账号。