我觉得权限管理是这个系统最主要,最核心的功能,其他的功能只是一些修饰,那如何完成数据权限的管理?下面来看看ruoyi是如何解决的,首先得先了解什么是RBAC模型(基于角色访问控制模型),如果有不明白的可以先学习一下。
这是ruoyi的数据库表划分,我用一个紫色的矩形表示了他是怎么通过在user和权限之间加入一层role来进行灵活的控制。
比如你的某个role前端访问的哪些菜单栏,那么就通过sys_menu和sys_role进行控制。部门同理,这个项目多半是基于部门来进行权限的划分,因为某个员工必属于一个部门
首先这个系统的权限分为五种:
全部数据权限:不进行数据过滤
自定义数据权限
本部门数据权限
本部门及以下数据权限
仅本人数据权限
自定义数据权限
主要来分析一下自定义数据权限,他是基于一张sys_role_dept表来映射角色id和部门id的,所以之前说大部分是通过部门来进行权限划分的,可以在前端界面的角色管理里进行权限分配。
核心代码是使用Spring的AOP来实现的,通过自定义注解@DataScope,注解具体功能实现在DataScopeAspect。
if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ",
deptAlias,role.getRoleId()));
}
可以看到他是通过我们当前登录用户的roleId来进行sql拼接的(可能会有多个role角色,所以这个代码在循环里面,下面例子中仅使用一个role)。
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<!-- 省略一些if判断 -->
<!-- 数据范围过滤 -->
${params.dataScope}
有了之前的AOP拼接的SQL,在使用mybatis的插值表达式插入到已有SQL中完成数据过滤。
比如这个用户查询,当前登录的用户角色是2,没有过滤前所有用户都能查询出来,然后加入了拼接的SQL后,仅仅返回用户的部门id在100,101,105,110之中的。
完整的SQL如下
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<!-- 数据范围过滤 -->
AND u.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 2 )
这样就完成了对用户查询的数据权限过滤。这是调用的代码。
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
除了这个调用,还能找到下列这些数据过滤,可以看到这些service方法调用对应的mapper文件都在SQL中使用了dept_id。可见是用了部门来进行数据权限的管理。
如果要我们在自己的系统中进行数据权限,也需要这么一个能让role标识的id。
其他的权限
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
{
StringBuilder sqlString = new StringBuilder();
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
}
}
部门数据权限
StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())
只能查看同一部门的,A用户在id=1部门,B用户在id=1部门,那A只能查询到自己和B
部门及以下数据权限
StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )"
,deptAlias, user.getDeptId(), user.getDeptId())
这里使用了一个字段ancestors,他是sys_dept表中的,表示上级所有的部门
那么你查询部门105,会将自己和110,11部门查询出来(find_in_set内置函数)。
自身数据权限
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
这里加个if判断主要是看自己的业务逻辑,比如在SysDeptServiceImpl和SysRoleSerivceImp中的方法就没有添加userAlias,当这个用户为仅自身数据权限且查看部门信息时,返回空数据集合。
总结
总的来说,实现数据权限管理,ruoyi还是做得不错的,可以直接当个脚手架使用。
在访问需要进行数据过滤的数据时,我们对部门进行拼接sql,仅仅查询出符合条件dept_id的数据。