最火后台管理系统 RuoYi 项目探秘,之四
上篇中,我们对 Shiro 框架做一个简单的扩展了解,稍微了解了一些 Shiro 的概念及运行逻辑,然后我们发现了在用户登录成功后,RuoYi 对用户授予了角色和菜单,这两个数据是如何来的,又是怎么样使用的,本篇将会进行探究。
RBAC
RBAC,就英文 Role-Based Access Control
的缩写,中文为“基于角色的访问控制”。这个名词的核心思想就是用户对系统的访问要进行控制,而控制方式是通过角色的方式进行。
一般基于角色的控制,会将粒度控制为比较粗的“角色”或者更细粒度的“权限”。在很多控制比较精细的系统中,角色是一组权限的集合,用户会与角色进行绑定,实际进行业务或者 API 访问时,会以具体的业务或 API 所对应的权限进行判定。而在控制稍微粗一点的系统中,就会直接使用角色进行访问控制的判断,比如直接将角色分为三个“系统管理员”、“部门管理员”和“普通用户”,三个角色能操作的业务或 API 在后台进行绑定、判断。
在上一篇中,我们已经看到 RuoYi 的实现中有如下代码:
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 角色加入AuthorizationInfo认证对象
info.setRoles(roles);
// 权限加入AuthorizationInfo认证对象
info.setStringPermissions(menus);
我们先追踪一下,RuoYi 到底是使用的角色还是权限进行控制。
查找 roleService
中的 selectRoleKeys
方法,我们可以看到对应的接口为 ISysRoleService
,相应的函数定义为:
/**
* 根据用户ID查询角色列表
*
* @param userId 用户ID
* @return 权限列表
*/
public Set<String> selectRoleKeys(Long userId);
在接口中声明函数访问范围为 public
,在 IDEA 中会直接被提示无效的声明,建议删除。作者坚持写了这么多无用的 public
,也是太难了。
在 selectRoleKeys
的实现中,我们可以看到,它是直接通过 mybatis 的 mapper 从数据库读取相应的数据:
/**
* 根据用户ID查询权限
*
* @param userId 用户ID
* @return 权限列表
*/
@Override
public Set<String> selectRoleKeys(Long userId)
{
List<SysRole> perms = roleMapper.selectRolesByUserId(userId);
Set<String> permsSet = new HashSet<>();
for (SysRole perm : perms)
{
if (StringUtils.isNotNull(perm))
{
permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
}
}
return permsSet;
}
这段代码中可以看中,最终是将 List<SysRole>
列表中每个 SysRole
的属性 roleKey
添加到一个集合中。同时,由于 roleKey
可能是一串由逗号分隔的字符串,还要先对 roleKey
进行逗号分割。而 roleKey
的注释为“角色权限”。可知,RuoYi 项目中应该是以权限进行访问控制的。
看一下 SysRole
的属性定义,代码如下:
public class SysRole extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 角色ID */
@Excel(name = "角色序号", cellType = ColumnType.NUMERIC)
private Long roleId;
/** 角色名称 */
@Excel(name = "角色名称")
private String roleName;
/** 角色权限 */
@Excel(name = "角色权限")
private String roleKey;
/** 角色排序 */
@Excel(name = "角色排序", cellType = ColumnType.NUMERIC)
private String roleSort;
/** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */
@Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
private String dataScope;
/** 角色状态(0正常 1停用) */
@Excel(name = "角色状态", readConverterExp = "0=正常,1=停用")
private String status;
/** 删除标志(0代表存在 2代表删除) */
private String delFlag;
/** 用户是否存在此角色标识 默认不存在 */
private boolean flag = false;
/** 菜单组 */
private Long[] menuIds;
/** 部门组(数据权限) */
private Long[] deptIds;
/** 角色菜单权限 */
private Set<String> permissions;