精华内容
下载资源
问答
  • 数据权限设计

    千次阅读 2014-03-02 20:45:46
    数据权限设计思考目前有关用户权限采用的比较多的都是基于RBAC模型 目前有关用户权限采用的比较多的都是基于RBAC模型,即通过对角色权限的定义完成对用户权限的限制。有关功能权限部分想必都比较清楚,就是将系统的...

    数据权限设计思考目前有关用户权限采用的比较多的都是基于RBAC模型

    目前有关用户权限采用的比较多的都是基于RBAC模型,即通过对角色权限的定义完成对用户权限的限制。有关功能权限部分想必都比较清楚,就是将系统的功能模块划分清楚,并赋予不同角色的访问权限,这样在用户访问某个功能模块之前进行权限校验即可。但是有关数据权限部分却一直比较模糊。

    在如下这篇文章中给出一个权限模型,里面提到了数据权限的建模。
    这个模型中有关数据权限也是对角色进行权限限定。模型中定义了几个概念:
    资源:用户将要访问的数据对象(如用户)
    数据对象类型:对用户将要访问的数据对象的限定类型(如部门)
    资源数据对象类型:上面两个概念关联产生的,即用户要访问什么类型的数据对象(如××部门的用户,但是此时的××是通用的,只有将数据对象类型具体化之后××才会出现)。

    将上面的资源数据对象类型实例化(即××具体化)之后就形成了一条数据对象访问规则,将这条规则附加给某个角色就完成了对角色数据访问权限的限定。这个模型定义是很清楚的,但是如何具体实现,其实是一个比较复杂的问题。

    一个比较直观的想法就是在业务层之上加一个数据权限校验层。

    上面定义的数据对象类型其实就是用户访问的数据对象的属性,因此在校验的时候确定相关数据对象的属性是否满足用户数据权限规则即可,对于增删改就是操作之前校验,对于查询就需要对查询结果过滤。当然为了整个实现的简单需要确保系统中的所有数据对象Class都是从一个Class中继承而来限制。这一想法实现起来比较简单,但是有一个限制和一个性能忧虑。限制就是为了保证规则校验的进行,必须使得所有数据对象都应当具有权限规则定义的相应属性,如果规则定义的属性发生变化,势必需要所有数据对象的生成方法发生变化。性能忧虑就是对查询结果的过滤,其实在一般的MIS系统中多数情况是进行查询,但是如果依照这种方法进行结果过滤的话,可能会存在性能的问题。

    因此,这一想法具有紧耦合和性能问题。

    另外一个想法依然无法避免紧耦合,但是可以避免性能问题。然而在逻辑上就没有上面那个直观了。这一想法就是将数据权限规则转成SQL语句,嵌入到数据访问层中去。

    因为资源必定会对应到某一个数据表,而数据对象类型也会对应的到某个数据表的某个属性列,因此完全可以依据数据权限规则动态生成SQL语句。数据权限规则可以如下定义:【资源数据对象类型 关系符 右值】,右值可以分为静态和动态两种:静态就是一个具体的值;动态就是用户的某个属性,这只有在与用户关联上之后才能确定。但是由于一个角色可以有多条数据权限规则,那么他们之后可以是与和或的关系,多个规则之间可能存在冲突,必须进行避免,如:当规则之间是与关系时,如果资源数据对象类型和关系符都相同时,就有可能冲突。具体的说就是要限制角色R只能访问A部门的用户且只能访问B部门的用户,这样子势必是相互冲突的。

    在数据访问层调用一个统一的SQL生成方法,传入两个参数:不加限定是要访问的数据表名和角色数据权限规则集(此时的规则集中应当已经具有准确的右值,即如果是动态的也已经根据用户属性赋值了)。方法是:依次判断规则集中的规则是否有要访问的数据表名,如果有就生成SQL语句的FROM子句和WHERE子句,最终语句规则集关系符(AND | OR)生成FROM子句和WHERE子句返回。
    数据访问层获得权限校验产生的FROM子句和WHERE子句嵌入到数据访问方法中去,这样就将数据访问和权限校验结合在一起了。明显是紧耦合,但是规避了性能问题。

    因此,上面两个想法各有个的优势,但是同样都有一定的问题,是不是有更好的办法?继续思考中……

     转载自http://peng4602.iteye.com/blog/680657

    展开全文
  • 数据权限设计研究-行数据权限

    万次阅读 2019-01-17 19:22:56
    数据权限设计研究-行数据权限关于权限设计功能权限数据权限前提数据分类几种场景设计方案与思路映射表提供过滤sql的方法测试实际应用查询新增修改删除修改数据的私有,公开,部门属性私有改为部门私有改为公开部门改...

    关于权限设计

    一般来说,权限模块对于每一个系统而言都是最基础的模块,根据项目需求和功能的不同,设计方案也有许多。但从大的方面来说,可以将权限分为两大类型:功能权限数据权限

    功能权限

    主要控制不同的资源主体(用户、角色、组织等)有操作不同的资源的权限。比如常见的不同的角色能访问不同的页面(菜单权限),以及具有操作同一页面的不同功能(按钮权限)等等。对于java开发而言,功能权限的开发相对来说要简单很多,有很多现成的框架可以实现。我推荐用shiro,因为简单易用,而且能实现按钮级别的控制。

    数据权限

    主要控制不同的资源主体(用户、角色、组织等)有查看不同的数据信息的权限。数据权限又分为数据行权限和数据列权限,本篇文章主要研究一下数据行权限的控制。

    前提

    数据权限一般和业务的关系非常紧密,可能不同的业务有不同的设计方案,所以很难有一种统一而使用简单的设计方案。我的想法是:基于角色-部门的控制方式。即拥有某个角色的人,能看见当前角色所包含的部门中的数据。为了更好的设计数据权限,我总结了一下几种数据。

    数据分类

    1. 公开数据:字面意思,就是公开的数据,不需要控制数据权限。
    2. 部门数据:属于某个部门的数据,只有部门的人员可以查看。
    3. 私有数据:用户自己的数据,只能自己查看。

    几种场景

    1. 某条数据属于多个部门的情况。
    2. 某领导可以跨部门查看数据。
    3. 可以查看子部门的数据。
    4. 私有数据可以分享给别人,部门,或者公开。

    设计方案与思路

    百度上一堆关于数据权限的设计方案,基本上都是基于用户-角色-部门这个来设计,我的思路也和这个差不多,用户与数据角色挂钩,数据角色与部门挂钩,这就比直接角色与部门挂钩要相对灵活一些。虽然某个用户只能属于一个部门,但是有可能出现上面提到的第2中场景,跨部门的情况。
    在这里插入图片描述
    我的设计思路是提供一个方法,写业务的人员需要将查询的表名传给这个方法,然后我返回一段sql,这段sql只是在原来的表上进行数据权限过滤,返回的数据字段和原表一模一样,然后业务代码编写者再把这个sql作为参数传递到DAO层,拼接到FROM后面或者JOIN后面即可。这样无论是单表查询还是多表查询,都可以实现数据权限控制。
    还有一种思路是就是用mybatis拦截器去拦截sql,然后对sql进行改造拼接,但是这样我需要去拦截每一条select的sql,可能会对性能有影响。
    为了少改原有的业务表,同时统一对系统中的表进行数据权限控制,我设计了一张映射表,来映射数据表,部门,用户之间的关系。

    映射表

    映射表字段如下

    字段名 类型 描述 备注
    ID 字符串 主键
    T_ID 字符串 数据表中的主键
    TABLE_NAME 字符串 数据表表名
    D_ID 字符串 部门ID主键
    U_ID 字符串 用户ID主键

    主键字段为字符串纯属个人习惯。
    有了这张映射表,我们就能根据映射表中的部门ID和用户ID是否为空来进行数据权限的识别,同时也能修改数据的所属权限(公开,部门,私有)。

    数据类型 部门ID 用户ID 备注
    公开数据
    部门数据 非空 非空
    私有数据 非空

    因为有私有数据分享给其他人或者部门,单条数据属于多个部门的情况,所以数据表与映射表应该是一对多的关系。

    提供过滤sql的方法

    代码如下

    /**
     * 数据权限sql拼接
     * 
     * @author chunhui.tan
     * @创建时间 2019年1月9日 下午4:30:09
     *
     */
    @Component
    public class DataSqlFilter {
    
    	@Autowired
    	private TsysUserRoleService tsysUserRoleService;
    
    	@Autowired
    	private TsysRoleDeptService tsysRoleDeptService;
    
    	@Autowired
    	private TsysDeptService tsysDeptService;
    
    	/**
    	 * 部门与数据表映射表的表名
    	 */
    	public final String MAPPING_TABLE = "DATA_MAPPING";
    
    	/**
    	 * 
    	 * @param tableName 表名
    	 * @param pkNmae    主键名
    	 * @param isPrivate 是否只获取私有
    	 * @param subDept   是否拥有子部门数据权限
    	 * @return
    	 */
    	public String getDataSql(String tableName, String pkNmae, Boolean isPrivate, Boolean subDept) {
    		// 校验参数
    		checkParam(tableName, pkNmae, isPrivate, subDept);
    		// 判断当前表是否开启数据权限
    		if (!isNeedPermissions(tableName)) {
    			return null;
    		}
    		// 获取当前用户
    		TsysUserEntity user = ShiroUtils.getUserEntity();
    		// 获取部门数据所需要的sql
    		String deptSql = getFilterSql(user, subDept);
    		StringBuilder dataSql = new StringBuilder();
    		dataSql.append("( SELECT DISTINCT S.* FROM ").append(tableName).append(" S LEFT JOIN ").append(MAPPING_TABLE)
    				.append(" T ON S.").append(pkNmae).append(" = T.T_ID AND T.TABLE_NAME= ").append("'").append(tableName)
    				.append("'");
    
    		if (isPrivate) {
    			// 只获取私有数据
    			dataSql.append(" WHERE (T.U_ID = ").append("'").append(user.getEmId()).append("'")
    					.append(" AND (T.D_ID='' OR T.D_ID IS NULL))");
    		} else {
    			// 正常数据:私有数据+部门数据+公开数据
    			dataSql.append(" WHERE T.D_ID IN ").append(deptSql).append(" OR (T.U_ID = ").append("'")
    					.append(user.getEmId()).append("'").append(" AND (T.D_ID='' OR T.D_ID IS NULL))")
    					.append(" OR ((T.D_ID='' OR T.D_ID IS NULL) AND (T.U_ID='' OR T.U_ID IS NULL))");
    		}
    		dataSql.append(")");
    		return dataSql.toString();
    
    	}
    
    	/**
    	 * 判断当前表是否开启数据权限
    	 * 
    	 * @param tableName
    	 * @return
    	 */
    	private Boolean isNeedPermissions(String tableName) {
    		// TODO 这里可以通过表名查询配置或者表来判断改表是否开启了数据权限
    		return true;
    	}
    
    	/**
    	 * 获取部门数据情况下的过滤条件SQL
    	 * 
    	 * @param user
    	 * @param subDept
    	 */
    	private String getFilterSql(TsysUserEntity user, Boolean subDept) {
    		// 部门ID列表
    		Set<String> deptIdList = new HashSet<>();
    		// 用户角色对应的部门ID列表
    		List<String> roleIdList = tsysUserRoleService.queryRoleIdList(user.getEmId());
    		if (roleIdList.size() > 0) {
    			List<String> userDeptIdList = tsysRoleDeptService
    					.queryDeptIdList(roleIdList.toArray(new String[roleIdList.size()]));
    			deptIdList.addAll(userDeptIdList);
    		}
    		// 用户子部门ID列表
    		if (subDept) {
    			List<String> subDeptIdList = tsysDeptService.getSubDeptIdList(user.getEmDeptId());
    			deptIdList.addAll(subDeptIdList);
    		}
    		List<String> result = deptIdList.stream().map(i -> {
    			return "'" + i + "'";
    		}).collect(Collectors.toList());
    		StringBuilder sqlFilter = new StringBuilder();
    		sqlFilter.append("(").append(StringUtils.join(result, ",")).append(")");
    		return sqlFilter.toString();
    	}
    
    	/**
    	 * 参数校验
    	 * 
    	 * @param tableName
    	 * @param pkNmae
    	 * @param isPrivate
    	 * @param subDept
    	 */
    	private void checkParam(String tableName, String pkNmae, Boolean isPrivate, Boolean subDept) {
    	//TODO 进行sql注入的校验
    		if (StringUtils.isBlank(tableName) || StringUtils.isBlank(pkNmae) || null == isPrivate || null == subDept) {
    			throw new XcrmsException("数据权限-缺少参数");
    		}
    	}
    }
    
    

    测试

    当前系统使用shiro做的权限,角色分成两种类型,一种是功能角色,一种是数据角色。功能角色与菜单按钮挂钩,数据角色与部门挂钩。接下来用PostMan接口测试方式来测试获取到的sql。

    只获取私有数据

    @Autowired(required = true)
    private DataSqlFilter dataSqlFilter;
    
    	/**
    	 * test
    	 * 
    	 * @param id
    	 * @return
    	 */
    	@GetMapping("/getDataSql")
    	public Result getDataSql() {
    		String dataSql = dataSqlFilter.getDataSql("PRODUCT", "P_ID", Boolean.TRUE, Boolean.FALSE);
    		System.out.println(dataSql);
    		return ResultUtil.success(dataSql);
    	}
    

    结果用Navicat处理一下:
    在这里插入图片描述
    获取普通数据
    所谓普通数据就是用户正常能看见的数据:私有数据+部门数据+公开数据。
    将参数isPrivate设置为false即可

    @Autowired(required = true)
    	private DataSqlFilter dataSqlFilter;
    
    	/**
    	 * test
    	 * 
    	 * @param id
    	 * @return
    	 */
    	@GetMapping("/getDataSql")
    	public Result getDataSql() {
    		String dataSql = dataSqlFilter.getDataSql("PRODUCT", "P_ID", Boolean.FALSE, Boolean.FALSE);
    		System.out.println(dataSql);
    		return ResultUtil.success(dataSql);
    	}
    
    

    结果用Navicat处理一下:
    在这里插入图片描述

    实际应用

    查询

    业务开发人员在查询是只需要调用这个方法获取到sql后,然后作为参数传入DAO层,在mybatis的xml文件中拼接即可(图片中的${dataSql}应为#{dataSql}),如下:
    在这里插入图片描述

    新增

    新增业务数据时,业务需要知道这个数据时那种数据类型,然后新增数据后,需要新增一条映射记录。提供过滤sql的类中可以提供统一的新增映射数据的方法。还没写,大概如下:

    	/**
    	 * 新增映射数据
    	 * 
    	 * @param pk_value  数据表数据主键值
    	 * @param tableName 数据表表名
    	 * @param type      数据类型 0 私有 1 公开 2 部门
    	 */
    	public void addMapping(String pk_value, String tableName, Integer type) {
    		// TODO 可以通过shiro获取到当前登录的用户,然后获取到用户的部门,然后根据type来新增映射关系
    
    	}
    
    

    修改

    若只是修改数据,则不关映射表的事。

    删除

    删除时需要注意,因为某条数据可能属于多个部门或者多个个人,那么当删除掉这条数据后,那么映射表中就存在多条T_ID相同和TABLE_NAME相同的数据,删除的时候应该通过T_ID和TABLE_NAME来删除映射表中的所有数据。

    修改数据的私有,公开,部门属性

    即修改映射表,按道理说,一般只会存在数据的所有人能修改,但是以防万一,业务开发人员需要判断当前数据的U_ID是否与当前用户的用户ID一致,不一致不能修改。当然,公开数据时不需要进行这一步校验的。以下修改方法都可以在DataSqlFilter类中统一提供

    私有改为部门

    即用户将私有数据分享给指定的部门
    修改方式:修改映射表中的D_ID为用户指定的部门ID

    私有改为公开

    即用户将私有数据全部公开
    修改方式:置空映射表中的U_ID和D_ID

    部门改为公开

    即将部门所拥有的数据公开
    修改方式:置空映射表中的U_ID和D_ID

    其他变更

    其他变更只要对照上面那张数据类型表即可进行修改。

    总结

    有人可能会说为什么不在原来的业务表上面加上部门ID和用户ID,从而不用映射表?
    但是这样会有一些问题,一是实际开发过程中,往往由于需求的变化,很难确定哪些表需要加部门ID和用户ID。二是这样无法满足一些特殊需求,比如:个人数据分享出去给其他人或者部门,单条数据属于多个部门。
    总结下来用以上方案来控制数据权限的优缺点如下
    优点:
    1.统一提供过滤sql,维护映射表,修改数据类型的方法,而且不用修改业务数据表,对原来的代码入侵最小化。
    2.不论是单表查询还是多表都能支持。
    3.比较灵活,针对不同需求,可以灵活调整过滤sql

    缺点:
    1.因为在对原表进行过滤时需要连接映射表,假如是多表联查,每张表都要连接映射表,可能对查询性能有影响。
    2.因为每条业务数据都会对应一条映射数据,那么意味着映射表将会有很多数据,在过滤的时候会影响性能,当然这里可以用分模块设计多个映射表的方法来解决。

    以上只是一个设计思路,还没用于实际项目,如有不足,欢迎斧正,谢谢。

    展开全文
  • 任何一个系统中,都离不开权限的设计权限设计 = 功能权限 + 数据权限+字段权限【功能权限】:能做什么的问题。如查询、增删改信息【数据权限】:能看到哪些数据的问题。如查看本人、部门团队、区域或者整个公司、...

            任何一个系统中,都离不开权限的设计

    权限设计 = 功能权限 + 数据权限+字段权限

    【功能权限】:能做什么的问题。如查询、增删改信息【数据权限】:能看到哪些数据的问题。如查看本人、部门团队、区域或者整个公司、甚至整个系统的数据【字段权限】:能看到哪些信息的问题。如联系人姓名,但是不能查看到联系人地址、联系人电话这样的

    • 1.功能权限设计

            常常是基于RBAC(Role-Based Access Control)的一套设计方案

    • 2.数据权限设计

    • 2.1数据权限设计分析

            根据不同的业务场景,则权限却不尽相同,应该根据具体的场景巧妙设计;且必须在项目开始时进行设计,不像功能权限一样,在项目结束的时候在追加。数据权限做不到组件级别,必须在项目设计阶段就已经规划好。之前看网上相同有人想基于SPRING切面的原理去实现数据权限,这样就能够做到了低侵入、低耦合,想法非常好。但是现实非常骨感,这样做使整个应用系统效率大减折扣,相同对数据权限的控制策略也非常不灵活

    • 2.2SQL语句可扩展,数据权限设计分析

            数据权限往往作为功能权限的高级行为。能够从数据对象的幅度方面进行控制。比方用户仅仅能看自己的订单、普通会员看不到某数据对象的高级属性(字段)等等。颗粒度这么细的情况下对结果集处理显然是不可能了,这时仅仅能介入到SQL语句中,此时又不想在开发阶段让开发者过多的考虑数据权限的问题,这个时候就需要将sql 和数据权限策略分开。再调用接口的时候,进行数据权限接口的拼接。这样也算做到的代码的低侵入。

            数据权限模块的核心之中的一个就有SQL语句的高效解析处理,SQL处理指依据当前登录人信息及数据权限策略生成一个带有数据权限处理结果的SQL语句。所以这里对SQL语句的解析处理必需要求精确、准确。在开发阶段由开发者把SQL写入到配置文件里,在执行阶段由数据权限取得该SQL进行分析处理(加上数据权限),这样就完毕了SQL的组装处理。

    • 2.3方案

    • 2.3.1 方案一:按照岗位体系建立数据权限

    把权限赋予岗位,再把员工(用户)放在岗位上,从而间接把权限赋予用户。

            有的企业的数据权限很简单,就是普通员工只能看到自己的数据,部门负责人可以看到本部门的数据,高层管理可以看到所有下级部门的数据。这样可以把这套规则直接写死在系统里面,然后根据员工的任职岗位和部门去读取对应的数据范围即可。

    e1871a0cf2ceee954c329e03ff9324f8.png

    此方案的典型适用场景就是销售管理系统、客户关系管理系统(CRM)

    特点:数据权限的划分严格按照员工岗位体系划分

    优点:设置简单,只需要录入需要限制的单据,选择是按照部门或者按照下属来限制即可。

    缺点:需要维护一套岗位体系;不够灵活,无法查看跨部门的数据、上级领导的数据等

    也就是说,使用岗位体系数据权限只有两种结果,要么受岗位体系限制,要么没有限制(能看到所有数据), 其他需要限制但不是按照岗位体系限制的需求,则无法满足

    比如有些集团中心的财务、人事等岗位,需要看到整个集团的数据,但是他们又不是集团领导,其他人也不是他们的下属,这种情况岗位体系数据权限是满足不了的

    • 2.3.2 方案二:针对角色设置数据权限

    把权限赋予角色,角色叠加到用户上,从而间接把权限赋予用户。

    角色和岗位相比,有两个好处:1、岗位是在企业组织架构里面设立的,不能随意修改,但是角色是可以灵活设置的,比如可以设置一个“华南大区报销负责人”的角色,但是这个岗位在企业组织架构中不存在,所以不能设立这样一个岗位。2、角色可以多个叠加,比如张三又负责华南大区的费用报销,又负责华东大区的费用报销,就可以把“华南大区报销负责人”和“华东大区报销负责人”两个角色都赋予张三。但是岗位上张三是一个“报销专员”,并没有身兼多职。

    所以,角色比岗位要灵活很多。

    将单据中的每一个字段都作为一个数据权限对象,然后对这些字段设置比较条件,这些比较条件组合起来就形成一个针对该单据的数据规则。每一个数据规则有一个名称。

    比如,我们可以设置一个数据规则,条件是:客户所在地区等于A,并且,客户状态为待续签。那么这条数据规则就可以看到A地区待续签的客户,我们可以把它命名为“A地区待续签”。

    所以,数据规则其实是某张单据的一个数据范围,也就是某部分的数据。

    abaf87a0b1040983e89d8934013d3a85.png

    比较条件可以设置变量,比如客户的业务员为“当前用户对应的业务员”,更灵活更方便维护。

    设置好数据规则之后,我们把这个数据规则跟角色关联起来,就可以限制该角色能看到的数据范围了。

    fb9bbe59239a34168a38f18ad66e32a0.png

    如果不设置数据权限,则默认能看到所有数据。

    如果有多个角色赋予同一个用户,且不同角色的数据权限不同,则取范围的并集。

    此方案完美解决了方案一的问题,可以通过设置角色的权限来灵活地控制每一个用户的权限,满足很多特殊化的场景。

    缺点:1、需要维护用户的角色;2、数据规则虽然可以用变量,如果是多层的计算逻辑,则无法满足。

    • 2.3.3 方案三:岗位数据权限和角色数据权限的结合

    企业的数据权限需求,无非就两种,有些数据是基于岗位体系划分数据权限的,有些数据是需要灵活设置的。所以我们把方案一和方案二结合起来,就形成了适用性更高的方案三。

    此方案中,可以对角色设置岗位体系数据权限,同时还可以对角色设置其他的数据规则。也就是说,岗位体系数据权限和数据规则权限可以灵活切换、叠加来设置。

    有的人可以只按照岗位体系数据权限来限制,有的人可以只按照数据规则来限制;有的人还可以又有岗位体系数据权限,又有数据规则权限。

    如果同一角色同一单据,又设置了岗位体系数据权限,又设置了数据规则权限,则取两者的交集。

    举例:

    企业内所有员工都要使用费用报销模块,要求普通员工只能看到自己的数据,领导可以看到直属下级的数据。同时集团的财务张三负责华南和华东的费用报销,李四负责华北和华中的费用报销。

    此时可以设置三个角色:

    • 角色“岗位费用报销”,设置岗位体系数据权限,选择直接下属,并把角色赋予除张三和李四外的所有用户。则这些用户可以看到自己的数据及自己直接下属的数据。(普通员工没有下属,只能看到自己的数据)。

    • 角色 “华南和华东的报销专员”,设置了数据规则为“报销部门为华南 或 报销部门为华东”,该角色赋予张三。

    • 角色 “华北和华中的报销专员”,设置了数据规则为“报销部门为华北 或 报销部门为华中”,该角色赋予李四。

    普通员工和领导有岗位体系数据权限,财务张三和李四有数据规则数据权限。

    76b899c4a9851b36af1b94bd789bb772.png

    22a7c315ceab5f2faade932d043a0a8a.png

    方案三综合了以上两者的优点,更加灵活便捷。很少企业是完全按照岗位体系来划分数据权限的,也很少企业所有的数据权限都可以用数据规则来限制,大多数是两种需求都有的情况。所以方案三的适用性更好,更适用于全员应用的系统。

    方案都有好坏,主要是看不同的系统及企业权限管理需求。核心方案是:数据范围划分清晰、准确,设置灵活、维护成本低。

    数据是一个企业最重要的资产。很多企业之间的竞争,其实也是数据之争,资源之争。数据权限,就如同为数据筑起的一座座城墙,清晰地划分了用户能看到的数据范围,为数据提供安全保障。

    展开全文
  • 关于权限设计一般来说,权限模块对于每一个系统而言都是最基础的模块,根据项目需求和功能的不同,设计方案也有许多。但从大的方面来说,可以将权限分为两大类型:功能权限和数据权限功能权限主要控制不同的资源主体...

    9e95a6a5f7dee76ff23c4ef41ea881a2.gif

    关于权限设计

    一般来说,权限模块对于每一个系统而言都是最基础的模块,根据项目需求和功能的不同,设计方案也有许多。但从大的方面来说,可以将权限分为两大类型:功能权限和数据权限

    功能权限

    主要控制不同的资源主体(用户、角色、组织等)有操作不同的资源的权限。比如常见的不同的角色能访问不同的页面(菜单权限),以及具有操作同一页面的不同功能(按钮权限)等等。对于java开发而言,功能权限的开发相对来说要简单很多,有很多现成的框架可以实现。我推荐用shiro,因为简单易用,而且能实现按钮级别的控制。

    数据权限

    主要控制不同的资源主体(用户、角色、组织等)有查看不同的数据信息的权限。数据权限又分为数据行权限和数据列权限,本篇文章主要研究一下数据行权限的控制。

    前提

    数据权限一般和业务的关系非常紧密,可能不同的业务有不同的设计方案,所以很难有一种统一而使用简单的设计方案。我的想法是:基于角色-部门的控制方式。即拥有某个角色的人,能看见当前角色所包含的部门中的数据。为了更好的设计数据权限,我总结了一下几种数据。

    数据分类

    公开数据:字面意思,就是公开的数据,不需要控制数据权限。

    部门数据:属于某个部门的数据,只有部门的人员可以查看。

    私有数据:用户自己的数据,只能自己查看。

    几种场景

    某条数据属于多个部门的情况。

    某领导可以跨部门查看数据。

    可以查看子部门的数据。

    私有数据可以分享给别人,部门,或者公开。

    设计方案与思路

    百度上一堆关于数据权限的设计方案,基本上都是基于用户-角色-部门这个来设计,我的思路也和这个差不多,用户与数据角色挂钩,数据角色与部门挂钩,这就比直接角色与部门挂钩要相对灵活一些。虽然某个用户只能属于一个部门,但是有可能出现上面提到的第2中场景,跨部门的情况。

    8d8e3b13233f31eebc81058eeaafb139.png

    我的设计思路是提供一个方法,写业务的人员需要将查询的表名传给这个方法,然后我返回一段sql,这段sql只是在原来的表上进行数据权限过滤,返回的数据字段和原表一模一样,然后业务代码编写者再把这个sql作为参数传递到DAO层,拼接到FROM后面或者JOIN后面即可。这样无论是单表查询还是多表查询,都可以实现数据权限控制。

    还有一种思路是就是用mybatis拦截器去拦截sql,然后对sql进行改造拼接,但是这样我需要去拦截每一条select的sql,可能会对性能有影响。

    为了少改原有的业务表,同时统一对系统中的表进行数据权限控制,我设计了一张映射表,来映射数据表,部门,用户之间的关系。

    映射表

    映射表字段如下

    ed7676032d55bee6de07b2525dec418d.png

    主键字段为字符串纯属个人习惯。有了这张映射表,我们就能根据映射表中的部门ID和用户ID是否为空来进行数据权限的识别,同时也能修改数据的所属权限(公开,部门,私有)。

    ddcb4364271c4dce488e3446f604988e.png

    因为有私有数据分享给其他人或者部门,单条数据属于多个部门的情况,所以数据表与映射表应该是一对多的关系。

    提供过滤sql的方法

    代码如下

    /**
    *
    数据权限sql拼接*
    *
    @author chunhui.tan
    *
    @创建时间 201919日 下午4:30:09
    */
    @Componentpublic class DataSqlFilter {
    @Autowiredprivate TsysUserRoleService tsysUserRoleService;@Autowiredprivate TsysRoleDeptService tsysRoleDeptService;@Autowiredprivate TsysDeptService tsysDeptService;/**
    *
    部门与数据表映射表的表名*/public final String MAPPING_TABLE = "DATA_MAPPING";/**
    *
    @param tableName 表名* @param pkNmae 主键名* @param isPrivate 是否只获取私有* @param subDept 是否拥有子部门数据权限* @return*/public String getDataSql(String tableName, String pkNmae,Boolean isPrivate, Boolean subDept) {// 校验参数checkParam(tableName, pkNmae, isPrivate, subDept);// 判断当前表是否开启数据权限if (!isNeedPermissions(tableName)) {return null;}// 获取当前用户TsysUserEntity user = ShiroUtils.getUserEntity();// 获取部门数据所需要的sqlString deptSql = getFilterSql(user, subDept);StringBuilder dataSql = new StringBuilder();dataSql.append("( SELECT DISTINCT S.* FROM ").append(tableName)
    .append(" S LEFT JOIN ").append(MAPPING_TABLE)
    .append(" T ON S.").append(pkNmae).append
    (" = T.T_ID AND T.TABLE_NAME= ").
    append("'").append(tableName)
    .append("'");
    if
    (isPrivate) {// 只获取私有数据dataSql.append(" WHERE (T.U_ID = ").append("'").
    append(user.getEmId()).append("'")
    .append(" AND (T.D_ID='' OR T.D_ID IS NULL))");} else {// 正常数据:私有数据+部门数据+公开数据dataSql.append(" WHERE T.D_ID IN ").append(deptSql).
    append(" OR (T.U_ID = ").append("'")
    .append(user.getEmId()).append("'").
    append(" AND (T.D_ID='' OR T.D_ID IS NULL))")
    .append(" OR ((T.D_ID='' OR T.D_ID IS NULL) " +"AND (T.U_ID='' OR T.U_ID IS NULL))");}
    dataSql.append(")");
    return
    dataSql.toString();}/**
    *
    判断当前表是否开启数据权限*
    *
    @param tableName
    *
    @return*/private Boolean isNeedPermissions(String tableName) {// TODO 这里可以通过表名查询配置或者表来判断改表是否开启了数据权限return true;}/**
    *
    获取部门数据情况下的过滤条件SQL
    *
    *
    @param user
    *
    @param subDept
    */
    private String getFilterSql(TsysUserEntity user, Boolean subDept) {// 部门ID列表Set deptIdList = new HashSet<>();// 用户角色对应的部门ID列表List roleIdList = tsysUserRoleService.
    queryRoleIdList(user.getEmId());
    if
    (roleIdList.size() > 0) {
    List userDeptIdList = tsysRoleDeptService
    .queryDeptIdList(roleIdList.toArray(newString[roleIdList.size()]));deptIdList.addAll(userDeptIdList);}// 用户子部门ID列表if (subDept) {
    List subDeptIdList = tsysDeptService.
    getSubDeptIdList(user.getEmDeptId());deptIdList.addAll(subDeptIdList);}
    List result = deptIdList.stream().map(i -> {return "'" + i + "'";}).collect(Collectors.toList());StringBuilder sqlFilter = new StringBuilder();sqlFilter.append("(").append(StringUtils.
    join(result, ",")).append(")");
    return
    sqlFilter.toString();}/**
    *
    参数校验*
    *
    @param tableName
    *
    @param pkNmae
    *
    @param isPrivate
    *
    @param subDept
    */
    private void checkParam(String tableName, String pkNmae,Boolean isPrivate, Boolean subDept) {//TODO 进行sql注入的校验if (StringUtils.isBlank(tableName) ||
    StringUtils.isBlank(pkNmae) ||null == isPrivate || null == subDept) {throw new XcrmsException("数据权限-缺少参数");}
    }
    }

    测试

    当前系统使用shiro做的权限,角色分成两种类型,一种是功能角色,一种是数据角色。功能角色与菜单按钮挂钩,数据角色与部门挂钩。接下来用PostMan接口测试方式来测试获取到的sql。

    只获取私有数据

    @Autowired(required = true)private DataSqlFilter dataSqlFilter;/**
    * test
    *
    *
    @param id
    *
    @return*/@GetMapping("/getDataSql")public Result getDataSql() {
    String dataSql = dataSqlFilter.
    getDataSql("PRODUCT", "P_ID", Boolean.TRUE, Boolean.FALSE);System.out.println(dataSql);
    return
    ResultUtil.success(dataSql);

    }

    结果用Navicat处理一下:

    c3178ef656be1cc1132ccb2477e9f0e3.png

    获取普通数据所谓普通数据就是用户正常能看见的数据:私有数据+部门数据+公开数据。将参数isPrivate设置为false即可

    @Autowired(required = true)private DataSqlFilter dataSqlFilter;/**
    * test
    *
    *
    @param id
    *
    @return*/@GetMapping("/getDataSql")public Result getDataSql() {
    String dataSql = dataSqlFilter.getDataSql
    ("PRODUCT", "P_ID", Boolean.FALSE, Boolean.FALSE);System.out.println(dataSql);
    return
    ResultUtil.success(dataSql);}

    结果用Navicat处理一下:77917866120d3a48ed13fc754ba2700b.png

    实际应用

    查询

    业务开发人员在查询是只需要调用这个方法获取到sql后,然后作为参数传入DAO层,在mybatis的xml文件中拼接即可(图片中的${dataSql}应为#{dataSql}),如下:a1fff91b30d2cd82421bc2a808e72a6e.png

    新增

    新增业务数据时,业务需要知道这个数据时那种数据类型,然后新增数据后,需要新增一条映射记录。提供过滤sql的类中可以提供统一的新增映射数据的方法。还没写,大概如下:

    /**
    *
    新增映射数据*
    *
    @param pk_value 数据表数据主键值* @param tableName 数据表表名* @param type 数据类型 0 私有 1 公开 2 部门*/public void addMapping(String pk_value,String tableName, Integer type) {// TODO 可以通过shiro获取到当前登录的用户,
    //
    然后获取到用户的部门,然后根据type来新增映射关系}

    修改

    若只是修改数据,则不关映射表的事。

    删除

    删除时需要注意,因为某条数据可能属于多个部门或者多个个人,那么当删除掉这条数据后,那么映射表中就存在多条T_ID相同和TABLE_NAME相同的数据,删除的时候应该通过T_ID和TABLE_NAME来删除映射表中的所有数据。

    修改数据的私有,公开,部门属性

    即修改映射表,按道理说,一般只会存在数据的所有人能修改,但是以防万一,业务开发人员需要判断当前数据的U_ID是否与当前用户的用户ID一致,不一致不能修改。当然,公开数据时不需要进行这一步校验的。以下修改方法都可以在DataSqlFilter类中统一提供

    私有改为部门

    即用户将私有数据分享给指定的部门

    修改方式:修改映射表中的D_ID为用户指定的部门ID

    私有改为公开

    即用户将私有数据全部公开

    修改方式:置空映射表中的U_ID和D_ID

    部门改为公开

    即将部门所拥有的数据公开

    修改方式:置空映射表中的U_ID和D_ID

    其他变更

    其他变更只要对照上面那张数据类型表即可进行修改。

    总结

    有人可能会说为什么不在原来的业务表上面加上部门ID和用户ID,从而不用映射表?

    但是这样会有一些问题,一是实际开发过程中,往往由于需求的变化,很难确定哪些表需要加部门ID和用户ID。二是这样无法满足一些特殊需求,比如:个人数据分享出去给其他人或者部门,单条数据属于多个部门。

    总结下来用以上方案来控制数据权限的优缺点如下

    优点:

    1.统一提供过滤sql,维护映射表,修改数据类型的方法,而且不用修改业务数据表,对原来的代码入侵最小化。

    2.不论是单表查询还是多表都能支持。

    3.比较灵活,针对不同需求,可以灵活调整过滤sql

    缺点:

    1.因为在对原表进行过滤时需要连接映射表,假如是多表联查,每张表都要连接映射表,可能对查询性能有影响。

    2.因为每条业务数据都会对应一条映射数据,那么意味着映射表将会有很多数据,在过滤的时候会影响性能,当然这里可以用分模块设计多个映射表的方法来解决。

    以上只是一个设计思路,还没用于实际项目,如有不足,欢迎斧正,谢谢。

    ————————————————

    版权声明:本文为CSDN博主「_飞飞飞飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

    原文链接:https://blog.csdn.net/qq_38846242/article/details/86529896

    ec09a6956dd1850f118747557695e078.png

    展开全文
  • 数据权限设计——基于EntityFramework的数据权限设计方案:一种设计思路 正文 前言:“我们有一个订单列表,希望能够根据当前登陆的不同用户看到不同类型的订单数据”、“我们希望不同的用户能看到不同时间段...
  • 产品权限设计——首先区分功能权限和数据权限权限管理系统是2B类产品必不可少的产品功能,牵涉整个产品的使用。权限管理功能不同的客户组织架构是完全不同的,功能的复用性很低,如何设计出高质量的权限管理功能是...
  • JAVA 数据权限设计

    2015-09-20 08:05:00
    数据权限设计 自序 在各种系统中。要保证数据对象的安全性以及易操作性,使企业的各业务部门、职能部门可以方便并且高效的协同工作,那么一个好的数据权限管理设计就成为一个关键的问题。尽管企业中各个单元的...
  • 一、前言几乎在任何一个系统中,都离不开权限的设计,权限设计 = 功能权限 + 数据权限,而功能权限,在业界常常是基于RBAC(Role-Based Access Control)的一套方案。而数据权限,则根据不同的业务场景,则权限却不尽...
  • 我们一般说【权限】的时候是在说功能权限和数据权限。功能权限指用户登录系统后能看到什么模块,能看到哪些页面,而数据权限指的是用户在某个模块里能看到几条数据,能看到哪些数据。以下分别描述一下我对功能权限和...
  • 数据权限设计,集成前端页面,后台数据结构。权限设计,基本参照。仅供学习使用
  • 上一篇给大家介绍了“功能权限”设计,本篇主要介绍“数据权限设计,做B端用户中心近半年,从一头雾水到产品上线,总结出来一些经验,希望能够给到大家一些帮助。“功能权限”控制的是用户登录系统后能看到哪些...
  • 1、数据权限概述1.1、什么是数据权限数据权限是指对系统用户进行数据资源可见性的控制,通俗的解释就是:符合某条件的用户只能看到该条件下对应的数据资源。那么最简单的数据权限大概就是:用户只能看到自己的数据...
  • 本文为我们介绍了功能权限和数据权限的不同点、以及不同部分中的要点与注意事项。做2B的系统总是不可回避的遇上权限问题,他不是核心业务却又必不可少,而且总是牵一发而动全身,更要命的是不同客户组织架构完全不同...
  • 接下来我将以角色、权限、以及RABC权限设计方法来概述权限设计、数据权限设计2个操作。了解权限系统前,首先要定义角色,角色的分类如下最高权限角色超级管理员,这个角色一般为平台创建者。可以拥有最高权限,可...
  • 本文为我们介绍了功能权限和数据权限的不同点、以及不同部分中的要点与注意事项。做2B的系统总是不可回避的遇上权限问题,他不是核心业务却又必不可少,而且总是牵一发而动全身,更要命的是不同客户组织架构完全不同...
  • 数据权限设计初探

    千次阅读 2012-02-26 23:52:07
    数据权限设计初探  李俊杰 概述 在许多项目中,都会涉及到数据权限问题,所谓数据权限是表示,在系统中即使角色相同,都有操作权限,但业务操作时受风险、额度、销售区域等业务属性限制。 如销售人员可以看到...
  • CRM-数据权限设计

    2019-07-06 13:48:37
    CRM-数据权限数据权限概念1.数据权限使用方式分类2.数据权限设置支持的权限3.数据权限模型4.数据权限场景描述5.数据权限配置解决方案6.数据权限的功能支持列表6.7数据权限使用规范 数据权限概念 对企业应用的各种...
  • 1、数据权限概述1.1 什么是数据权限? 数据权限是指对系统用户进行数据资源可见性的控制,通俗的解释就是:`只有符合条件的用户才能看到该条件下对应的数据资源`.举个简单的例子: 本组织的销售人员只能看见本组织...
  • 功能权限 1.介绍 每个controller接口,对应权限表一条记录。项目启动时,通过springmvc提供的类,可以获取到有权限注解的方法,从而构造权限对象,插入数据库。 2.代码 接口注解 权限初始化类 @Component @Slf4j ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,664
精华内容 3,465
关键字:

数据权限设计