Jeecg如何实现数据权限/隔离?用户上下文变量没有user_id?修改源码

前言:

在使用如若依、Jeecg等后台管理系统进行二次开发的时候,我们总会涉及到数据隔离相关的内容,如每个非管理员用户应该都只能看到自己创建的数据,而不是所有的数据,本文将以jeecg为例像大家介绍如何每个用户之间的数据隔离的效果。

先从功能需求说起,当查询房屋列表的时候,每个房东(用户)应该只能看到自己的房屋(house)表数据,而房屋和房东的关系存放于房东(house_holder)表中,所以我们需要通过两个表的关联来实现数据隔离的查询。而jeecg中要如何实现呢?我们一起啦看看吧

基于Jeecg实现:

先放出官方文档中跟数据隔离有关的内容:http://doc.jeecg.com/2044046

  1. 进入菜单管理页面,找到想要进行数据隔离的菜单,点击添加一个下级并进行配置

image.png
选择按钮/权限并配置菜单路径(笔者能力有限暂时无法弄清菜单路径和授权表示的作用在哪)
image.png
然后对添加的这个按钮/权限进行数据规则的配置,
由于我们需要要从另外一张表中去查出跟user_id有关的house_id所以要使用自定义SQL的方式

image.png

  1. 在角色管理中对用角色进行数据权限的授权
    给需求中的房东用户配置上数据权限

image.png

  1. 在后端对应的controller中的list方法中加上@PermissionData注解,该注解有一个pageComponent参数为数据隔离的菜单的前端组件路径,如果配置了只有该前端组件路径会被拦截,不配置的话就拦截全部请求。

大家可能注意到了,官方文档中给出了系统上下文变量有sys_user_code, 有sys_user_name,有sys_org_code但就是没有SQL表达式中的#{sys_user_id},而我们想要实现的效果只能给予user_id来实现,那么我们要怎么实现呢?

修改底层源码

笔者先给出如何修改,实现将当前登录用户的id存入数据权限的系统上下文变量中,来探讨其源码是如何进行修改的。

以下文件直接通过idea的search every where 的功能即可找到对应类

3.1. SysUserCacheInfo中添加一个String类型的sysUserId和其对应的setter/getter

...
private String sysUserId;

// 用idea生成setter/getter,如果懒的话直接用@Data然后删除其他的setter/getter也成
public String getSysUserId() {
   return sysUserId;
}

public void setSysUserId(String sysUserId) {
   this.sysUserId = sysUserId;
}
...

3.2. SysBaseApiImpl中的getCachUser为3.1的CacheInfo的新增的属性赋值

public SysUserCacheInfo getCacheUser(String username) {
   SysUserCacheInfo info = new SysUserCacheInfo();
   info.setOneDepart(true);
   LoginUser user = this.getUserByName(username);
   if(user!=null) {
      //加上下面一行代码
      info.setSysUserId(user.getId());
      info.setSysUserCode(user.getUsername());
      ...
   }
   ...

3.3. DataBaseConstant中添加数据权限的系统上下文的key值

...
public static final String SYS_USER_CODE_TABLE = "sys_user_code";
/**
* 添加下面这一行代码
*/
String SYS_USER_ID = "sys_user_id";

public static final String SYS_USER_NAME = "sysUserName";
...

3.4. 在JwtUtil中的getUserSystemData放将sys_user对应的value设置为当前用户的id

...
if(key.equlas(...){

}else if (key.equals(DataBaseConstant.TENANT_ID) || key.toLowerCase().equals(DataBaseConstant.TENANT_ID_TABLE)){
  ...
   }
// 加上这一个else if 的判断就完成啦
}else if (key.equals(DataBaseConstant.SYS_USER_ID)){
    returnValue = sysUser.getId();
}
...

基于上面的4个步骤的小改动,我们就能够成功的为jeecg的数据权限添加上当前用户id值了。然后进行测试应该就能发现我们成功进行了基于user_id的多表关联的数据隔离啦。

底层深探

通过对@PermissionData注解的跳转,我们能看到其对应着一个名为PermissionDataAspect的切面

image.png

在切面中查询出了对应用户的数据权限模型(PermissionDataRuleModel),并且于当前用户的userInfo都放入了request请求中

image.png
数据权限模型的值如下,可以看到,其值就是我们刚刚在后台中配置的数据规则

image.png

而JeecgDataAuthorUtils.installUserInfo的代码如下:

image.png

其将userinfo存放在了request当中(setter),但该切面到此处就结束了。 我们再看看整个进入Controller的list方法:

/**
 * 分页列表查询
 *
 * @param house
 * @param pageNo
 * @param pageSize
 * @param req
 * @return
 */
@AutoLog("房产主表-分页列表查询")
@ApiOperation(value="房产主表-分页列表查询", notes="房产主表-分页列表查询")
@GetMapping("/list")
   @PermissionData
public IPage<House> queryPageList(House house,
                        @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
                        @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
                        HttpServletRequest req) {
   QueryWrapper<House> queryWrapper = QueryGenerator.initQueryWrapper(house, req.getParameterMap());
   Page<House> page = new Page<House>(pageNo, pageSize);
   IPage<House> pageList = houseService.page(page, queryWrapper);
   return pageList;
}

有一个QueryGenerator.initQueryWrapper其传入了Request的ParameterMap,这个parameterMap中包含了在切面是存入的userInfo和datarulemodel,所以数据的隔离会不会是在这里实现的呢? 我们在进去看看,

image.png

再进入这里installMplus方法:

image.png

看到这,我们应该就找到了jeecg中居于mybatis plus实现的数据权限/隔离的代码所在地啦,但这个类十分的冗余判断,难以阅读,所以作者给出几个该类中和数据权限有关的代码:

installMplus方法中通过我们之前在切面中传入的SysPermissionDataRuleModel开始解析我们自定义的SQL语句中的模板变量。

image.png
这个getSqlRuleValue方法就是模板替换的核心啦

public static String getSqlRuleValue(String sqlRule){
   try {
      //获取sql语句中的模板变量:实际就是通过正则匹配sqlRule中的${}字符
      Set<String> varParams = getSqlRuleParams(sqlRule);
      for(String var:varParams){
         // 根据模板变量的key值来返回具有实际意义的value:通过JwtUtil的getSystemData方法来返回value
         String tempValue = converRuleValue(var);
         // 替换模板变量为真正的value
         sqlRule = sqlRule.replace("#{"+var+"}",tempValue);
      }
   } catch (Exception e) {
      log.error(e.getMessage(), e);
   }
   return sqlRule;
}

public static String converRuleValue(String ruleValue) {
   String value = JwtUtil.getUserSystemData(ruleValue,null);
   return value!= null ? value : ruleValue;
}

getSqlRuleValue方法的debug变量如下
image.png

JwtUtil中的getSystemData的代码如下:其实就是一大堆的if-else + contstant常量罢了

/**
  * 从当前用户中获取变量
 * @param key
 * @param user
 * @return
 */
public static String getUserSystemData(String key,SysUserCacheInfo user) {
   if(user==null) {
      user = JeecgDataAutorUtils.loadUserInfo();
   }
   // 获取登录用户信息
   LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();

  ...一些正则匹配
  
   //替换为系统登录用户帐号
   if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
      
   }
    ... 一些对于DataBaseContants中的KEY的else-if判断
  
   }else if (key.equals(DataBaseConstant.SYS_USER_ID)){
       returnValue = sysUser.getId();
       }
       
   return returnValue;
}

总结

数据隔离实际上就是对SQL进行拼接,如果只是对每个权限挨个写一个SQL的话,谁都会。其难点其实主要在于如何在不污染业务代码的情况下完成SQL的拼接,Jeecg通过切面 + MyBatisPlus的QueryWrapper进行实现了这样的效果。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值