修改若依的数据权限功能
若依(cloud版本)的数据权限功能是通过注解实现的,在需要数据权限的方法上加上注解。
在注解中判断当前用户的角色对应的数据权限类型,在执行的sql语句后面拼接部门和用户的sql过滤条件从而实现数据权限功能。
数据权限的核心功能的代码如下:
如果当前用户有多个角色,那么多个角色之间不同的数据权限通过sql的or来连接。
最后在mybatis的xml中通过拼接sql语句实现。
这个功能没有问题,使用$来拼接sql也没有问题,因为数据权限中需要拼接的语句是写死的,而且若依也做了防注入的操作。
但是我们的客户验收项目的时候会做代码扫描,这种通过$来拼接sql语句的情况是不允许出现的,哪怕这么写完全没有注入风险也不可以。
所以要找到另一种方法替换若依当前的数据权限功能。
网上翻了翻其他的成品框架,决定升级mybatis为myabtis-plus,使用mybatis-plus的内置拦截器来实现。数据权限的核心功能。
写一个自定义拦截器,实现myabtis-plus的内置拦截器InnerInterceptor ,就可以在sql语句执行前拦截到sql相关信息 ,在这里进行数据权限的操作。
先上拦截器的参考代码:
@Component
public class DataScopeInnerInterceptor implements InnerInterceptor {@Autowiredprivate RedisService redisService;@Autowiredprivate DataScopeService dataScopeService;//这个service的功能是使用feign调用auth模块中的代码,也就是下面代码的getUserDataScope方法。在登录的时候获得当前数据权限的角色id和部门id@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) {String originalSql = boundSql.getSql();PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);//带有datascope注解的mapper方法名Object cacheObject1 = redisService.getCacheObject(UserConstants.DATA_SCOPE_ANNO);String scopeMethod = String.valueOf(cacheObject1);//获得所执行sql语句的mapper方法全限定名String mapperMethod = ms.getId();mapperMethod = mapperMethod.substring(mapperMethod.lastIndexOf(".")+1);if (scopeMethod.contains(mapperMethod)) {//获得当前登录用户LoginUser loginUser = SecurityUtils.getLoginUser();if (null != loginUser){//项目启动和登录时没有用户SysUser sysUser = loginUser.getSysUser();Object cacheObject = redisService.getCacheObject(UserConstants.USER_DATA_SCOPE + sysUser.getUserId());//如果redis中没有存,就再调用方法存一次if (null == cacheObject) {dataScopeService.getUserDataScope(loginUser);cacheObject = redisService.getCacheObject(UserConstants.USER_DATA_SCOPE + sysUser.getUserId());}String scoprStr = String.valueOf(cacheObject);if ("#".equals(scoprStr)) {//查询全部数据originalSql = String.format("SELECT * FROM (%s) temp_data_scope",originalSql);}else {String[] split = scoprStr.split("#");if (scoprStr.length()>1 && scoprStr.startsWith("#")) { //只有对当前用户的数据权限 originalSql = String.format("SELECT * FROM (%s) temp_data_scope WHERE temp_data_scope.user_id = %s", originalSql,split[1]);} else if (scoprStr.length()>1 && scoprStr.endsWith("#")) { //只有对部门的数据权限(包括本部门,本部门及子部门和自定义) originalSql = String.format("SELECT * FROM (%s) temp_data_scope WHERE temp_data_scope.dept_id IN (%s)", originalSql,split[0]);} else if (!scoprStr.startsWith("#") && !scoprStr.endsWith("#")) { //既有部门的数据权限又有用户的数据权限originalSql = String.format("SELECT * FROM (%s) temp_data_scope WHERE temp_data_scope.dept_id IN (%s) or temp_data_scope.user_id = (%s)",originalSql, split[0],split[1]);}}mpBs.sql(originalSql);}}}
}
这个拦截器默认拦截所有的sql语句操作,可以看到,这个是通过把原有的sql语句作为子查询来实现数据权限的,如果这样实现有以下问题需要解决:
1,子查询中的字段不能是重复的字段名,比如用户表和部门表关联,那么用户的user_id和部门的user_id不能在一起,必须有一个要起别名。所以要修改原有的语句。
2,若依之前的注解是有参数的,参数可以指定数据权限是过滤部门id还是用户id,
@DataScope(deptAlias = "d", userAlias = "u")
比如如果数据权限是“只有当前用户”的时候,按照上面的代码是在子查询外面加WHERE temp_data_scope.user_id = %s,这样就要求语句中一定要有user_id这个字段,就需要userAllias这个参数,如果没有这个参数,如果只有deptAlias参数,数据权限是不过滤的。
我打算把若依的注解的参数去掉,所以要在需要数据权限的语句中添加用户id和部门id,如果没有就关联用户表和部门表然后加上id。
3,拦截器默认拦截所有sql操作,所以要在在过滤器中找到哪些方法是加了若依之前的数据权限注解的,修改后的数据权限也是需要对加了注解的方法才进行过滤。
解决方法如下:在项目启动的时候扫描所有加注解的方法名,并redis缓存
参考代码:
@Component
public class PackageUtil {@AutowiredRedisService redisService;@PostConstructpublic void doTheJob(){getDatascopeAnnotationMethodName("com.tbenefitofcloud.system");}private void getDatascopeAnnotationMethodName(String packageName) {Set<Class<?>> classSet = getClassSet(packageName);List<String> anno = new ArrayList<>();for (Class<?> clazz : classSet) {Method[] declaredMethods = clazz.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {String isNotNullStr = "";// 判断是否方法上存在注解 MethodInterfaceboolean annotationPresent = declaredMethod.isAnnotationPresent(DataScope.class);if (annotationPresent) {// 获取自定义注解对象DataScope methodAnno = declaredMethod.getAnnotation(DataScope.class);// 根据对象获取注解值anno.add(declaredMethod.getName());}}}System.out.println(anno);String join = StringUtils.join(anno.toArray(), ",");redisService.setCacheObject(UserConstants.DATA_SCOPE_ANNO,join);}/*** 获取类加载器*/public ClassLoader getClassLoader() {return Thread.currentThread().getContextClassLoader();}/*** 加载类*/public Class<?> loadClass(String className, boolean isInitialized) {Class<?> cls;try {cls = Class.forName(className, isInitialized, getClassLoader());} catch (ClassNotFoundException e) {throw new RuntimeException(e);}return cls;}/*** 获取指定包名下的所有类*/public Set<Class<?>> getClassSet(String packageName) {Set<Class<?>> classSet = new HashSet<Class<?>>();try {Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));while (urls.hasMoreElements()) {URL url = urls.nextElement();if (url != null) {String protocol = url.getProtocol();if (protocol.equals("file")) {String packagePath = url.getPath().replaceAll("%20", " ");addClass(classSet, packagePath, packageName);} else if (protocol.equals("jar")) {JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();if (jarURLConnection != null) {JarFile jarFile = jarURLConnection.getJarFile();if (jarFile != null) {Enumeration<JarEntry> jarEntries = jarFile.entries();while (jarEntries.hasMoreElements()) {JarEntry jarEntry = jarEntries.nextElement();String jarEntryName = jarEntry.getName();if (jarEntryName.endsWith(".class")) {String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");doAddClass(classSet, className);}}}}}}}} catch (Exception e) {throw new RuntimeException(e);}return classSet;}private void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {File[] files = new File(packagePath).listFiles(new FileFilter() {public boolean accept(File file) {return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();}});for (File file : files) {String fileName = file.getName();if (file.isFile()) {String className = fileName.substring(0, fileName.lastIndexOf("."));if (StringUtil.isNotEmpty(packageName)) {className = packageName + "." + className;}doAddClass(classSet, className);} else {String subPackagePath = fileName;if (StringUtil.isNotEmpty(packagePath)) {subPackagePath = packagePath + "/" + subPackagePath;}String subPackageName = fileName;if (StringUtil.isNotEmpty(packageName)) {subPackageName = packageName + "." + subPackageName;}addClass(classSet, subPackagePath, subPackageName);}}}private void doAddClass(Set<Class<?>> classSet, String className) {Class<?> cls = loadClass(className, false);classSet.add(cls);}
}
在登录的时候查找出当前用户对应角色所需的用户id和部门id,并缓存
参考代码:
public void getUserDataScope(LoginUser userInfo) {SysUser sysUser = userInfo.getSysUser();List<SysRole> roles = sysUser.getRoles();//该用户对应部门idList<Long> deptIdsByRoleId = new ArrayList<>();String scopeType = "";String userId = "";//用户id,数据权限为仅本人数据权限时使用for (SysRole role : roles) {scopeType = role.getDataScope();if ("2".equals(scopeType)){ //自定数据权限,从sys_role_dept表中查询List<Long> scopeDeptIds = remoteScopeService.getDeptIdsByRoleId(role.getRoleId());deptIdsByRoleId.addAll(scopeDeptIds);}else if ("3".equals(scopeType)){ //部门数据权限//当前角色对应部门iddeptIdsByRoleId.add(sysUser.getDeptId());}else if ("4".equals(scopeType)){ //部门及下属部门List<Long> deptAndChild = remoteScopeService.findDeptAndChild(sysUser.getDeptId());deptIdsByRoleId.addAll(deptAndChild);}else if ("5".equals(scopeType)){//仅本人数据权限userId = sysUser.getUserId().toString();}}Set<Long> scopeDepts = new HashSet<>(deptIdsByRoleId);String join = StringUtils.join(scopeDepts.toArray(), ",");System.out.println(join);redisService.setCacheObject(UserConstants.USER_DATA_SCOPE+sysUser.getUserId(),join+"#"+userId);}
修改若依的数据权限功能相关推荐
- Jeesite框架实用 如何实现本公司内数据权限功能使用
文章目录 前言 一.数据库中创建一张视图表!!!(非常重要) 二.创建自己的表 三.使用jeesite导入表,生成代码 四.在此表生成的,service层中,加上过滤 五.在此表生成的,control ...
- Ruoyi数据权限功能实现
若依实现数据实现实例 我们都知道,在一个系统中存在着不同的用户,而不同的用户在一个页面中所看到的数据是完全不一样的 而若依的数据权限是按照不同的部门进行实现的 我们先看普通用户的数据权限 可以看见他只 ...
- 信息系统开发平台OpenExpressApp - 数据权限
功能权限一般放在一般是放在MVC的controller层,在<信息系统开发平台OpenExpressApp - 功能权限>介绍了OEA基本的功能权限,我们是通过统一配置的.作为数据应用产品 ...
- 基于mybatis拦截器实现数据权限
数据权限是很多系统常见的功能,实现的方式也是很多的,最近在做项目的时候,自己基于mybatis拦截器做了一个数据权限的功能. **功能设计 a) 需要做数据权限功能的表加上一个权限id字段. 权限i ...
- 通过DataEase行列权限设置实现数据权限管控
在企业的日常经营中,企业人数达到一定数量之后,就需要对企业的层级和部门进行细分,建立企业的树形组织架构.围绕着树形组织架构,企业能够将权限落实到个人,避免企业内部出现管理混乱等情况.而在涉及到数据分析 ...
- 报表数据权限体系的搭建,报表平台权限管理
为了数据系统安全,我们通常都希望每个用户只能看到其所属机构的数据. 比如对银行来说,希望广州分行的用户登录系统打开报表只能看到广州分行的数据,北京分行的用户登录系统只能看到北京分行的数据.下面以Sma ...
- 实现权限控制_SpringCloud 微服务实现数据权限控制
前章讲了如何进行用户权限验证<SpringCloud下的用户鉴权方案>,它是微服务下统一资源访问权限的控制,就像一道墙保护着SpringCloud集群下的各个业务应用服务.而本章要讲的是权 ...
- 基于ruoyi的数据权限设置
目录 一.代码修改 1.生成代码 2.代码修改 二.权限使用和测试 1.部门设置 三.测试 一.代码修改 基于ruoyi实现数据权限的话,首先需要一系列代码,既单表的增删改查 1.生成代码 用这个表来 ...
- Dataphin核心功能(四):安全——基于数据权限分类分级和敏感数据保护,保障企业数据安全
简介: <数据安全法>的发布,对企业的数据安全使用和管理提出了更高的要求.Dataphin提供基于数据分级分类和数据脱敏的敏感数据识别和保护能力,助力企业建立合规的数据安全体系,保障企业数 ...
最新文章
- word分散对齐调整宽度_Word中文字很难对齐?有了这4个方法,2秒可对齐Word中所有文字...
- 华为 日志服务器 配置文件,配置日志服务器
- 澳大利亚悉尼大学徐畅教授招收深度学习方向全奖博士生
- 6、Cocos2dx 3.0游戏开发的基本概念找个小三场比赛
- vmware克隆虚拟机
- linux上设置了log4j没有产生日志文件_关于 log4j 升级到 log4j2 的小结
- windows 命令收集
- 《程序员修炼之道》笔记(九)
- h5 端图片上传-模拟多张上传
- linux之SQL语句简明教程---SUBSTRING
- SQL Server元数据损坏(metadata corruption)修复
- 人寿保险分红型的十年能取出来吗?
- matlab将常值函数转换为变量,MATLAB与科学计算期末复习题题库15.11.12
- AI 终极问题:我们的大脑是一台超级计算机吗?
- Tampermonkeych插件看B站无地区限制,加速arxiv下载论文速度
- 三星s7edge计算机软件,三星s7edge 官方6.0固件
- 项目管理软件-禅道-内网部署
- dts音效大师安卓版_dts音效大师
- win7配置本地ftp服务器
- 冶金物理化学复习 --- 熔渣的基础理论