权限的定义:
数据表分析:
1. 实体表
1.1 用户表 sys_user
1.2 角色表 sys_role
1.3 菜单表 sys_menu
1.4 部门表 sys_dept
2.联系表
2.1 用户-角色关联表 sys_user_role
2.2 角色-菜单关联表 sys_role_menu
2.3 角色-部门关联表 sys_role_dept
sys_user(用户表):存储系统用户的基本信息,包括用户ID、用户名、密码,所属部门id等。
sys_role(角色表):存储角色的基本信息,包括角色ID、角色名称等。
sys_menu(菜单表):存储系统菜单的信息,包括菜单ID、菜单名称、父菜单ID、菜单类型等。
sys_dept(部门表):存储部门的信息,包括部门ID、部门名称、父部门ID等。
sys_user_role(用户角色关联表):用于建立用户和角色之间的关联关系,记录用户拥有的角色。(在创建用户的时候可勾选角色和部门)
sys_role_menu(角色菜单关联表):用于建立角色和菜单之间的关联关系,记录角色拥有的菜单权限。
sys_role_dept(角色部门关联表):记录自定义数据权限时这个角色可以查看的部门信息。在自定义数据权限中勾选的部门信息会存储在该表中。
总结:总的来说可以理解为 角色是权限的实体,用户是角色的唯一作用对象,而用户同时又归属于部门,并且又拥有自己的职位,一个用户可以有多个角色,单一个用户只能属于一个部门,一个部门又可以拥有多个用户。
部门角色绑定表是记录自定义数据权限而创建的表结构,主要记录的是某个角色可以查看那些部门的信息。
并不能理解为部门有那些角色就拥有那些权限!(如果理解不了往下看完全篇后再来理解这句)
数据表相当于只是简单的定义了权限 ,要想实现页面的控制就要看我们如何依赖这些信息做相应的逻辑处理,而若依提出了一些简便的权限控制方案。
若依的权限分为接口调用权限和数据范围操作权限 若想彻底搞懂往下看吧!
ruoyi前后端不分离版本-权限的控制:
接口调用权限
注解控制权限:
Shiro
注解权限控制
@RequiresAuthentication
使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。@RequiresGuest
使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是gust身份,不需要经过认证或者在原先的session中存在记录。@RequiresPermissions
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。@RequiresRoles
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。@RequiresUser
当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
@RequiresRoles
@RequiresRoles
注解用于配置接口要求用户拥有某(些)角色才可访问,它拥有两个参数
参数 | 类型 | 描述 |
value | String[] | 角色列表 |
logical | Logical | 角色之间的判断关系,默认为Logical.AND |
示例1: 以下代码表示必须拥有admin
角色才可访问
@RequiresRoles("admin")
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
示例2: 以下代码表示必须拥有admin
和common
角色才可访问
@RequiresRoles({"admin", "common"})
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
示例3: 以下代码表示需要拥有admin
或common
角色才可访问
@RequiresRoles(value = {"admin", "common"}, logical = Logical.OR)
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
@RequiresPermissions
@RequiresPermissions
注解用于配置接口要求用户拥有某(些)权限才可访问,它拥有两个参数
参数 | 类型 | 描述 |
value | String[] | 权限列表 |
logical | Logical | 权限之间的判断关系,默认为Logical.AND |
示例1: 以下代码表示必须拥有system:user:add
权限才可访问
@RequiresPermissions("system:user:add")
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
示例2: 以下代码表示必须拥有system:user:add
和system:user:edit
权限才可访问
@RequiresPermissions({"system:user:add", "system:user:edit"})
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
示例3: 以下代码表示需要拥有system:user:add
或system:user:edit
权限才可访问
@RequiresPermissions(value = {"system:user:add", "system:user:edit"}, logical = Logical.OR)
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
提示
Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关) RequiresRoles、RequiresPermissions、RequiresAuthentication、RequiresUser、RequiresGuest。
例如:你同时声明了RequiresRoles
和RequiresPermissions
,那就要求拥有此角色的同时还得拥有相应的权限。
理解补充:
1.这里的用户角色标识字符串和权限标识字符串在前端浏览器定义后会分别存储再角色表和菜单表中。
我们要想在后端编写接口后定义权限可以在pc端查看或则在数据库中查看。
2.可以理解为RequiresPermissions是对RequiresRoles的一种再细分,因为一个角色可以被赋予多个权限标识,而权限标识是不可再细分的最小颗粒。
编程式判断资源访问权限
示例: 编程式判断资源访问权限
Subject subject = ShiroUtils.getSubject();
subject.isPermitted(permission) // 验证是否有资源权限
subject.isPermittedAll(permissions) // 验证是否有资源权限(列表)
// 例如:
if (subject.isPermitted("sys:user:edit"))
{
System.out.println("当前用户有编辑用户权限");
}
示例: 编程式判断角色访问权限
Subject subject = ShiroUtils.getSubject();
subject.hasRole(role) // 验证是否有角色权限
subject.hasRoles(roles) // 验证是否有角色权限(列表)
// 例如:
if (subject.hasRole("admin"))
{
System.out.println("当前用户有admin角色权限");
}
基于URI判断用户访问权限
示例: 基于URI判断用户访问权限
// 验证是否有资源权限
filterChainDefinitionMap.put("/system/user/add", "perms[system:user:add]");
filterChainDefinitionMap.put("/system/user/**", "perms[system:user:*]");
// 验证是否有资源权限(列表),相当于isPermitedAll()方法。
filterChainDefinitionMap.put("/system/user/**", "perms[system:user:add,system:user:edit]");
示例: 基于URI判断角色访问权限
// 验证是否有角色权限
filterChainDefinitionMap.put("/system/user/**", "roles[admin]");
// 验证是否有角色权限(列表),相当于hasAllRoles()方法。
filterChainDefinitionMap.put("/system/user/**", "roles[admin,common]");
URI通配符
? :匹配一个字符。
* :匹配零个或多个字符串。
** :匹配路径中的零个或多个路径。
疑问思考?
1.如果一个接口同时可以被两个或多个角色可调用,但是这角色又来自不同的部门,要查看各自的部门信息我门该如何做呢?
前面介绍在若依中用户是角色分配的唯一实体,若依的用户表中有所属部门id字段,我们可以根据部门id做为条件去要查询业务信息表,既然要以部门id为条件,显然要查询的业务信息表中要有部门id这个字段。
我们可以将条件写为 p_id(部门id)=user.getPid();
2.但是如果这个角色不仅可以看到本部门,还能看到部门的下级,我们该怎么做呢?
同样我们可以从用户信息中获取用户所属部门id,然后以该部门为父id 去部门表中查询出所有下级部门,然后再去业务信息表中查询
查询条件为 p_id in(...);
3.如果这个角色可以查看所有的部门信息呢?
显然不加查询条件即可
4.那么我们可以自定义角色的数据权限吗?
当然可以,例如角色A属于a部门,我只想让他看到b部门下的b1部门,和a部门下的信息。
我们可以将角色id和他能看到的所有部门信息绑定,再进行信息查询时只需查询这张关联表获取所有的部门id,然后以此为条件去要查询的信息表中查询即可。
观察上面的sql我们发现再同一个业务表的信息查询接口下,要是想实现基于角色的数据范围控制,只需要查询条件不同即可,为了提高sql的复用性,我们可以将sql条件做成动态sql,然后前端只需给我们传递角色的数据权限标识(若依中这个权限标识存储在角色表的datascope字段中)然后我们去拼接不同的sql片段。
同样若依就是基于sql拼接原理提供了数据权限控制的方案,可以简化我们的业务逻辑,而且会根据角色配置的不同数据权限,自动拼装sql。不用开发者去考虑如何根据角色的数据权限配置去编写sql。
数据权限
在实际开发中,需要设置用户只能查看哪些部门的数据,这种情况一般称为数据权限。
例如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制, 对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如设置只能看本公司、或者本部门的数据,对于特殊的领导,可能需要跨部门的数据, 因此程序不能硬编码那个领导该访问哪些数据,需要进行后台的权限和数据权限的控制。
提示
默认系统管理员admin
拥有所有数据权限(userId=1)
,默认角色拥有所有数据权限(如不需要数据权限不用设置数据权限操作)
数据权限是与角色关联的,在pc端配置角色信息的时候可选的配置数据权限
数据权限使用
1、在(系统管理-角色管理)设置需要数据权限的角色 目前支持以下几种权限(给角色添加数据权限)
- 全部数据权限
- 自定数据权限
- 部门数据权限
- 部门及以下数据权限
- 仅本人数据权限
2、在需要数据权限控制方法上添加@DataScope
注解,其中d
和u
用来表示表的别名
dataScope的切面类详细解析:当apI方法上添加该注解后会对api方法增强 会在执行接口方法前。自动执行切面中的通知方法。 看完数据权限之后看会更好理解
package com.ruoyi.framework.aspectj;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.context.PermissionContextHolder;
/**
* 数据过滤处理
*
* @author ruoyi
*/
@Aspect
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser))
{
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
//如果permission为空 那么permission的值为接口权限中的权限字符串
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias(), permission);
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param deptAlias 部门别名
* @param userAlias 用户别名
* @param permission 权限字符
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
/**需知
* 根据前面的权限定义可知:
* 用户只能属于一个部门,可有多个角色 记住这句话对后面代码分析很重要
*
*/
//用于存储sql条件
StringBuilder sqlString = new StringBuilder();
//存储用户所拥有的所有数据权限标识
List<String> conditions = new ArrayList<String>();
//存储用户所有设置了自定义权限的角色id并且这些角色有调用当前接口的权限
List<String> scopeCustomIds = new ArrayList<String>();
//初始化scopeCustomIds
user.getRoles().forEach(role -> {
/**
* role 的getpermisssion会在用户信息初始化的时候查询数据表将角色所有的菜单信息初始化到角色实体类中
这里是判断角色是否是自定义数据权限 并且有当前接口访问权限
*/
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
scopeCustomIds.add(Convert.toStr(role.getRoleId()));
}
});
//sql拼接
for (SysRole role : user.getRoles())
{
//获取角色的数据权限标识
String dataScope = role.getDataScope();
//判断标识是否已存在 思考:为什么权限字符串存在就跳过呢?
/*
* 这就回到了我们开始的那段话,一个用户可以有多个角色,但是一个用户只能归属于一个部门,如果对这句话有疑问去看数据表
* 假如用户有三个角色 a,b,c
* 角色a: 数据权限查看本部门
* 角色b:本部门及以下
* 角色c:本部门
*显然a和c的sql条件是一样的 因为无论用户是a角色还是b角色用户所属的部门是不变的,所以我们无需拼装重复的sql
* 但是对于c角色而言它不仅可以本部门还有本门以下数据 所以我我们需要查询子部门来进行数据的拼装
*
* */
if (conditions.contains(dataScope))
{
continue;
}
//如果permission 为我们自定义的permisssion值 需要在for循环结束调用我们自己的逻辑代码片段 稍后讲解
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
//如果有可查询所有数据的权限 则无需查询条件 结束循环 这里很好理解不在解释
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
//如果使用若依自定义权限
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
//上面已经解释过该集合的意义
//这里不难理解不再解释
if (scopeCustomIds.size() > 1)
{
// 多个自定数据权限使用in查询,避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
}
else
{
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(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
//保存权限标识
conditions.add(dataScope);
}
// 角色传递的权限标识非法不显示任何数据(不存在在id为0的部门所以查询结果必为空)
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
/**
* 这里我们可以根据自己定义的permission来设计自己的sql以达到数据过滤的效果
*/
//编辑写自定义代码
//eg:
if(StringUtils.isEmpty(permission)&&!permission.equals(PermissionContextHolder.getContext())){
//permission 不为空 并且不等于接口中的权限字符串
//... 写自己的逻辑
}
//将sql存入BaseEntity的Params中 在XXmappper.xml 使用${params.dataScope}可拼装最总sql条件
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
/**
* 拼接权限sql前先清空params.dataScope参数防止注入
*/
private void clearDataScope(final JoinPoint joinPoint)
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}
注解参数说明
参数 | 类型 | 默认值 | 描述 |
deptAlias | String | 空 | 部门表的别名 |
userAlias | String | 空 | 用户表的别名 |
permission | String | 接口权限字符串 | 权限字符 |
部门数据权限注解
@DataScope(deptAlias = "d")
public List<...> select(...)
{
return mapper.select(...);
}
部门及用户权限注解
@DataScope(deptAlias = "d", userAlias = "u")
public List<...> select(...)
{
return mapper.select(...);
}
3、在mybatis
查询底部标签添加数据范围过滤
<select id="select" parameterType="..." resultMap="...Result">
<include refid="select...Vo"/>
<!-- 数据范围过滤 -->
${params.dataScope}
</select>
例如:用户管理(未过滤数据权限的情况):
select u.user_id, u.dept_id, u.login_name, u.user_name, u.email
, u.phonenumber, u.password, u.sex, u.avatar, u.salt
, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by
, u.create_time, u.remark, d.dept_name
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
例如:用户管理(已过滤数据权限的情况):
select u.user_id, u.dept_id, u.login_name, u.user_name, u.email
, u.phonenumber, u.password, u.sex, u.avatar, u.salt
, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by
, u.create_time, u.remark, d.dept_name
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
)
结果很明显,我们多了如下语句。通过角色部门表(sys_role_dept)
完成了数据权限过滤
and u.dept_id in (
select dept_id
from sys_role_dept
where role_id = 2
)
逻辑实现代码 com.ruoyi.framework.aspectj.DataScopeAspect
提示
仅实体继承BaseEntity
才会进行处理,SQL
语句会存放到BaseEntity
对象中的params
属性中,然后在xml
中通过${params.dataScope}
获取拼接后的语句。
PS:如果是自己的业务表需要实现数据权限,需要有dept_id
和user_id
这两个字段。
那么permission有什么作用呢?
阅读DataScopeAspect源码不难发现,如果我们没有在datascope中设置permission属性默认它就是接口权限中的权限字符串,如果我们设置了permission属性那么就是我们自己设置的permission。
对于前者而言默认的permission值与若依的自定义权限有关它的值就是接口权限中的权限字符串,
若用户有多个角色,会将所有自定义角色并且有接口权限的角色id作为查询条件之一。
对于后者而言与我们自定义的数据权限相关,需要我们在角色遍历完之后对permisssion做自己的sql拼装。
示例:
开发部申请的项目不仅部门人员可见还需要审核部特定审核员可见(开发部审核部没有上下级所属关系),如何实现呢?
当然这里我们可以根据给角色配置若以的自定义数据权限然后再将这个角色分配给审核员即可(这种方法无需我们配置permission属性)
那如果有多个审核员我们需要在浏览器上挨个为他们配置权限,这无疑是非常繁琐的,有什么优化方案吗?
可以借助permission属性,并且要查询的业务表中有与userId或部门id关联的属性列
1:假如项目表中有审核员的userId,还有部门表id
2:要进行数据权限控制的api接口改造
@DataScope(deptAlias = "t1",permission = "t1.opId")//为这个表取别名 opId为业务表中的操作人id
@GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId)
{
...;
}
3:DataScopeAspect.改造
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param deptAlias 部门别名
* @param userAlias 用户别名
* @param permission 权限字符
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
scopeCustomIds.add(Convert.toStr(role.getRoleId()));
}
});
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (conditions.contains(dataScope))
{
continue;
}
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
if (scopeCustomIds.size() > 1)
{
// 多个自定数据权限使用in查询,避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
}
else
{
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(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
conditions.add(dataScope);
}
// 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
//开始改造
//添加对自定义permission验证
//permission 不为空 并且permission不为菜单中设置的权限信息(接口权限信息)
//再调用这个数据过滤方法之前如果permission为空会设置为等于PermissionContextHolder.getContext()这个上下文中存储着接口权限限制的表达式
if(StringUtils.isEmpty(permission)&&!permission.equals(PermissionContextHolder.getContext())){
sqlString.append(StringUtils.format("or {}={}",permission,user.getUserId()));
}
//改造结束
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
ruoyi-vue前后端分离版本-权限控制:
权限注解
Spring Security
提供了Spring EL
表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限。
权限方法
@PreAuthorize
注解用于配置接口要求用户拥有某些权限才可访问,它拥有如下方法
方法 | 参数 | 描述 |
hasPermi | String | 验证用户是否具备某权限 |
lacksPermi | String | 验证用户是否不具备某权限,与 hasPermi逻辑相反 |
hasAnyPermi | String | 验证用户是否具有以下任意一个权限 |
hasRole | String | 判断用户是否拥有某个角色 |
lacksRole | String | 验证用户是否不具备某角色,与 isRole逻辑相反 |
hasAnyRoles | String | 验证用户是否具有以下任意一个角色,多个逗号分隔 |
使用示例
其中@ss
代表的是PermissionService(opens new window)服务,对每个接口拦截并调用PermissionService
的对应方法判断接口调用者的权限。
- 数据权限示例。
// 符合system:user:list权限要求
@PreAuthorize("@ss.hasPermi('system:user:list')")
// 不符合system:user:list权限要求
@PreAuthorize("@ss.lacksPermi('system:user:list')")
// 符合system:user:add或system:user:edit权限要求即可
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")
编程式判断是否有资源权限
if (SecurityUtils.hasPermi("sys:user:edit"))
{
System.out.println("当前用户有编辑用户权限");
}
- 角色权限示例。
// 属于user角色
@PreAuthorize("@ss.hasRole('user')")
// 不属于user角色
@PreAuthorize("@ss.lacksRole('user')")
// 属于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")
编程式判断是否有角色权限
if (SecurityUtils.hasRole("admin"))
{
System.out.println("当前用户有admin角色权限");
}
同理:Permi类似角色权限的再细分
权限提示
超级管理员拥有所有权限,不受权限约束。
公开接口
如果有些接口是不需要验证权限可以公开访问的,这个时候就需要我们给接口放行。
使用注解方式,只需要在Controller
的类或方法上加入@Anonymous
该注解即可
// @PreAuthorize("@ss.xxxx('....')") 注释或删除掉原有的权限注解
@Anonymous
@GetMapping("/list")
public List<SysXxxx> list(SysXxxx xxxx)
{
return xxxxList;
}
数据权限与老版本一致