1)点击若依的系统用户管理页面,测试各种数据权限生成的sql,若依调用的后台方法是:
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
修改角色表中的data_scope字段,进行各种数据权限的设置。由于spring security会将用户的角色信息存起来,需要用户重新登陆。
不能用admin用户测试
测试结果:
DATA_SCOPE_ALL 全部数据权限
SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0'
DATA_SCOPE_CUSTOM 自定义数据权限
SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0' AND (d.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = 2))
去关联了表sys_role_dept去查询当前角色和部门的对应关系,在系统中对应的操作界面是
DATA_SCOPE_DEPT 部门数据权限
SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0' AND (d.dept_id = 101)
DATA_SCOPE_DEPT_AND_CHILD 部门及以下数据权限
SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0' AND (d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = 101 OR find_in_set(101, ancestors)))
DATA_SCOPE_SELF 仅本人数据权限
SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0' AND (u.user_id = 3)
2)原理
根据测试结果,数据权限的控制就是根据部门或者人员信息来进行的,也就是说,想要进行数据权限过滤的数据必须包含部门编号dept_id或者人员编号user_id。根据系统总结的5种数据类型,生成sql语句,通过在mybatis中添加${params.dataScope} 把生成的sql语句拼接到原sql里。
3) 实现 注解+AOP
a、需要使用到的数据表:sys_user\sys_role\sys_user_role\sys_dept\sys_role_dept。
b、这里要能保证用户能登陆并且能获取到自己的角色信息,权限后面会单独说这里只进行了简单处理能满足当前使用。权限方面的处理,sysuser实现了UserDetails接口;新增了UserDetailServiceImpl 实现了UserDetailsService;提供了pssswordEncoder 密码工具。
c、给请求参数,拼接params参数(通过继承baseEntity实现),把拼接号的sql通过params的字段dataScope传入到mybatis的xml中,实现了拼接sql。核心的切面处理方法就是在根据数据权限类型,来拼接sql,传递sql参数。
@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) {
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
private void handleDataScope(JoinPoint point, DataScope controllerDataScope) {
// 获取用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SysUser currentUser = (SysUser) authentication.getPrincipal();
if(currentUser!=null && !currentUser.isAdmin()){
dataScopeFilter(point, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
private void dataScopeFilter(JoinPoint point, SysUser currentUser, String deptAlias, String userAlias) {
StringBuilder sqlString = new StringBuilder();
// 考虑多个角色的数据权限可能重复的情况
List<String> conditions = new ArrayList<String>();
for (SysRole role : currentUser.getRoles())
{
String dataScope = role.getDataScope();
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StrUtil.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(StrUtil.format(" OR {}.dept_id = {} ", deptAlias, currentUser.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
sqlString.append(StrUtil.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, currentUser.getDeptId(), currentUser.getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StrUtil.isNotBlank(userAlias))
{
sqlString.append(StrUtil.format(" OR {}.user_id = {} ", userAlias, currentUser.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StrUtil.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
conditions.add(dataScope);
}
if (StrUtil.isNotBlank(sqlString.toString()))
{
Object params = point.getArgs()[0];
if (params!=null && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
/**
* 因为这个参数是后台拼接出来的,在接受到参数的时候先清除了由后台生成
* 避免参数注入
*
* @param point
*/
private void clearDataScope(final JoinPoint point) {
Object params = point.getArgs()[0];
if (params != null && params instanceof BaseEntity) {
BaseEntity base = (BaseEntity) params;
base.getParams().put(DATA_SCOPE,"");
}
}
}
View Code
4)测试
@RestController
@RequestMapping("/system/user")
public class SysUserController {
@Autowired
private ISysUserService userService;
@GetMapping("/list")
public AjaxResult list(SysUser user)
{
List<SysUser> list = userService.selectUserList(user);
AjaxResult ajax = AjaxResult.success();
ajax.put("list",list);
return ajax;
}
}
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return mapper.selectUserList(user);
}
spring security默认的认证模式formloggin,这里要在页面上进行登陆,然后请求:http://localhost:8080/system/user/list。同样修改sys_role的data_scope值后,需要重新访问登陆页面进行登陆。
5)总结
数据权限过滤其实可能平常业务代码中通过写死sql的方式实现过,若依的这种实现和业务关联的比较紧密,还需要请求参数继承BaseEntity,使用场景上应该尽量少用。
代码地址:https://github.com/hunji/RYMirror/releases/tag/2.4%E6%95%B0%E6%8D%AE%E6%9D%83%E9%99%90