精华内容
下载资源
问答
  • 2、数据权限。而数据权限又可在行和列上细分为两块,即数据范围权限:用户能看到哪些行的记录;数据字段权限:用户能看到这些行对应的哪些字段。本文以字段权限为例做一个demo展示。方案字段权限方案也有很多种,...

    前言

    项目实践中,基本都会有权限的需求,权限需求分为两大块:1、功能权限;2、数据权限。而数据权限又可在行和列上细分为两块,即数据范围权限:用户能看到哪些行的记录;数据字段权限:用户能看到这些行对应的哪些字段。本文以字段权限为例做一个demo展示。

    方案

    字段权限方案也有很多种,这里采用配置无权限字段,在sql查询前,对sql进行拦截过滤,剔除无权限字段。

    框架

    这里采用mybatis作为ORM框架。

    配置

    配置pom依赖

    org.mybatis.spring.boot

    mybatis-spring-boot-starter

    2.0.0

    com.github.jsqlparser

    jsqlparser

    1.2

    yml配置

    spring:

    proifles: dev

    #据源配置

    datasource:

    name: test

    url: jdbc:mysql://xxx:3306/xxx?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai

    username: root

    password: xxx

    # 使用druid数据源

    type: com.alibaba.druid.pool.DruidDataSource

    driver-class-name: com.mysql.jdbc.Driver

    # 注意:一定要对应mapper映射xml文件的所在路径

    mybatis:

    mapper-locations: classpath:mybatis/mapper/*.xml

    #注意:对应实体类的路径

    type-aliases-package: com.xxx.model

    #mybatis 配置路径

    config-location: classpath:mybatis/mybatis-config.xml

    plugin配置

    配置mybatis-config.xml

    /p>

    "http://mybatis.org/dtd/mybatis-3-config.dtd">

    插件实现

    import java.lang.reflect.Field;

    import java.util.ArrayList;

    import java.util.Iterator;

    import java.util.List;

    import java.util.Properties;

    import org.apache.ibatis.executor.Executor;

    import org.apache.ibatis.mapping.BoundSql;

    import org.apache.ibatis.mapping.MappedStatement;

    import org.apache.ibatis.plugin.Interceptor;

    import org.apache.ibatis.plugin.Intercepts;

    import org.apache.ibatis.plugin.Invocation;

    import org.apache.ibatis.plugin.Plugin;

    import org.apache.ibatis.plugin.Signature;

    import org.apache.ibatis.session.ResultHandler;

    import org.apache.ibatis.session.RowBounds;

    import net.sf.jsqlparser.JSQLParserException;

    import net.sf.jsqlparser.parser.CCJSqlParserUtil;

    import net.sf.jsqlparser.statement.Statement;

    import net.sf.jsqlparser.statement.Statements;

    import net.sf.jsqlparser.statement.select.PlainSelect;

    import net.sf.jsqlparser.statement.select.Select;

    import net.sf.jsqlparser.statement.select.SelectBody;

    import net.sf.jsqlparser.statement.select.SelectExpressionItem;

    import net.sf.jsqlparser.statement.select.SelectItem;

    //指定连接的方法

    @Intercepts(@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,

    RowBounds.class, ResultHandler.class }))

    public class DataAuthorInterceptor implements Interceptor {

    private Properties properties;

    @Override

    public Object intercept(Invocation invocation) throws Throwable {

    final Object[] args = invocation.getArgs();

    MappedStatement ms = (MappedStatement)args[0];

    Object parameterObject = args[1];

    BoundSql boundSql = ms.getBoundSql(parameterObject);

    //获取初始sql

    String originSql = boundSql.getSql();

    //修改sql

    String changeSql = parseSql(originSql);

    Field field = boundSql.getClass().getDeclaredField("sql");

    field.setAccessible(true);

    field.set(boundSql, changeSql);

    return invocation.proceed();

    }

    //解析SQL,根据数据权限,去除没有权限的字段

    private String parseSql(String sql) {

    List blockList = getBlockCols();

    StringBuffer newSql = new StringBuffer();

    try {

    //解析sql 获得结构化statement

    Statements s = CCJSqlParserUtil.parseStatements(sql);

    //对每一个statement

    for(Statement st : s.getStatements()) {

    if(null != st) {

    //查询sql处理

    if(st instanceof Select) {

    SelectBody selectBody = ((Select) st).getSelectBody();

    if(selectBody instanceof PlainSelect) {

    Iterator it = ((PlainSelect) selectBody).getSelectItems().iterator();

    //遍历查询字段

    while(it.hasNext()) {

    SelectItem si = it.next();

    if(si instanceof SelectExpressionItem) {

    for(String str : blockList) {

    //查询字段同 权限隐藏字段匹配 则从查询中移除

    if(si.toString().contains(str)) {

    it.remove();

    }

    }

    }

    }

    }

    //return ((Select) st).getSelectBody().toString();

    }

    newSql.append(st + ";");

    }

    }

    } catch (JSQLParserException e) {

    e.printStackTrace();

    }

    return newSql.toString();

    }

    //解析sql结构体

    private List getBlockCols(){

    List l = new ArrayList();

    l.add("a");

    l.add("b1");

    return l;

    }

    @Override

    public Object plugin(Object target) {

    return Plugin.wrap(target, this);

    }

    @Override

    public void setProperties(Properties properties) {

    this.properties = properties;

    }

    public static void main(String[] args) {

    String sql = "select a, b, c from table1";

    String sql2 = "select a, b, c from (select e as a, f as b, g as c from table1)";

    String sql3 = "select a as a1, b b1, c from table3 as t3, (select e as a, f as b from table1) , (select g as c from table2)";

    String sql4 = "select c.vin8, (select auto_brand from vin_base b where b.vin8 = c.vin8)\n" +

    "from vin_base c where c.vin8 = '72753413' ";

    String sql5 = "select a, b from table1 union select c, d from table2;";

    String changeSql = new DataAuthorInterceptor().parseSql(sql5);

    System.out.println("changeSql>>>>>>>>" + changeSql);

    }

    }

    到这里,完成了对sql的拦截修改。

    总结

    1、配置:pom.xml依赖配置、yaml应用配置、插件配置、插件实现。

    2、以上示例仅对一般查询及子查询做了处理,对与union查询,需要再实现。

    拦截处理,是一种很好的设计思路,可以对相同处理方式进行组件化,并在应用中,避免侵入式,从而做到润物无声。

    本文地址:https://blog.csdn.net/weixin_42005602/article/details/107151625

    如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

    展开全文
  • 1、数据权限概述1.1、什么是数据权限?如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java...

    1、数据权限概述

    1.1、什么是数据权限?

    如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

    数据权限是指对系统用户进行数据资源可见性的控制,通俗的解释就是:符合某条件的用户只能看到该条件下对应的数据资源。那么最简单的数据权限大概就是:用户只能看到自己的数据。而在正式的系统环境中,会有很多更为复杂的数据权限需求场景,如:

    领导需要看到所有下属员工的客户数据,员工只能看自己的客户数据;

    经理A能看到所有企业客户,经理B只能看到年销售额小于1000万的企业客户;

    角色A能看到全国的产品数据,角色B只能看到上海的产品数据;

    上述这些需求,使用硬编码也是可以实现的,但是在业务快速发展的过程中,类似这种数据权限需求会越来越多,如果全部采用硬编码的方式,无疑会给我们带来巨大的开发和维护压力。

    1.2、要素分析

    从当前登录用户的角度来说,数据权限的定义可以解释为:当前登录的用户只能看到该用户权限范围内的数据资源。由此可以分析出数据权限控制中几个关键要素:

    主体,即当前登录用户。领导、角色等概念可翻译为当前登录用户是否是领导,是否拥有某角色。

    数据资源。即受管控系统数据。

    条件规则。即当前登录用户对于某特定的数据资源适用的条件。

    2、数据权限设计

    理论上来说,用户在访问受控的系统数据时,获取用户对该数据资源适用的条件规则,并将该条件规则解析为SQL查询语句即可实现对数据的权限控制。但是在实现过程中,还是会有很多难点,譬如当前登录用户适用下列规则:

    客户数据:[客户经理] [包含于] [下属人员]产品数据:[销售地区] [等于] [上海]订单数据:([产品销售地区] [等于] [上海])[并且] ([客户市场经理] [包含于] [下属人员])

    思考如下问题:

    [客户经理] [包含于] [下属人员]如何解析为SQL语句?多表联合查询时又该如何处理?

    [下属人员]由系统根据当前登录用户计算而来,上海由管理员后台选择。两种方式如何兼容?

    对于复杂多变的组合条件,应该如何设计?

    如何确定当前查询应该应用哪些条件规则?

    一个用户拥有多个角色,不同角色对于同一个规则设置不同的值应该如何处理?

    2.1、规则元

    名词定义:规则元。在本文是指单个独立的数据规则定义,不同用户对规则元可设置具体的规则过滤值,该值用作数据查询时的筛选条件。上述规则中[客户经理],[销售地区]都属于规则元。

    2.2、规则元配置

    1.规则元名称的配置。一个表中哪些字段可以进行规则设置,以及规则元名称如何与表字段关联。(如上述规则中[客户经理],[销售地区]),比较容易想到的方法是通过配置文件维护规则名称与数据库字段之间的关系。

    2.规则元Value数据源的配置。如上述规则中的[下属人员],[上海],不难发现规则元Value来源有三种情况:

    后台管理人员输入。

    系统提供数据源,后台管理人员选择。如:所在地区[上海]

    系统提供数据。如:[下属人员]

    配置文件可以实现数据规则的配置需求,但是当规则元越来越多时,维护配置文件就会变得麻烦起来,我们是否可以效仿spring-boot取代spring-mvc的做法,使用注解来代替配置呢?每一条数据规则最终都会落到对数据库字段的控制,而现在绝大部分系统都会有一个Model层对应到数据库中的表,于是脑补出一个绝佳的规则元配置方式:

    @TableName("test")

    public class TestModal extends AbstractModel {

    @DataRule(name = "规则元名称")

    private String name;

    }

    @DataRule注解源码如下:

    @Target({ElementType.FIELD})

    @Retention(RetentionPolicy.RUNTIME)

    public @interface DataRule {

    /**

    * 规则元名称

    */

    String name() default "";

    /**

    * 规则元值来源类型

    */

    RuleSourceStrategy strategy() default RuleSourceStrategy.TEXT;

    /**

    * 当数据来源是用户选择时{@code RuleSourceStrategy.CHOICE}数据地址

    */

    String url() default "";

    /**

    * 当数据来源是系统提供时{@code RuleSourceStrategy.SYSTEM}提供器类名

    */

    Class extends IDataRuleProvider> provider() default NullDataRuleProvider.class;

    }

    系统启动时,将规则元配置信息(名称、对应数据表、对应字段、值来源类型,值来源url,值来源提供者类名等)同步至数据库。数据表简单设计如下图:

    2029dc8927493e1a212a9d4dacb850ba.png

    2.3、数据规则的配置

    有了规则元信息,管理人员即可在系统中针对不同用户(角色)设置规则元Value,该值作为数据查询时的筛选条件。规则元Value数据源包含三种情况,其中第①、②种情况下,需要管理员填写或选择该规则的值,存储于数据库;第③种情况下,Value值根据当前登录用户计算得出,也即是@DataRule注解中provider计算得来的值。由数据库存储的规则与系统计算得到的规则合并后即是登录用户的所有数据规则。

    一个简单的配置界面如下:

    40c1da59d63a76d35f004c09f58082d1.png

    2.4 数据规则的解析

    由上文可知,适用于当前登录用户的数据规则主要来源有两种:

    存储在数据库中的规则配置;如:所在地区[上海]

    需要系统计算的规则配置;如:[下属人员]

    两种情况下获取的数据规则合并之后即可获取适用于当前登录用户的数据规则集合,流程图如下:

    df023b4daa964d820c00edacca308c2d.png

    两种情况下获取的数据规则如何兼容?规则合并后成为一个复杂的查询条件应该如何设计?

    定义通用的规则结构如下:

    {

    rule:[{

    field: "name",

    operate: "equal",

    value: "xxx"

    }],

    operate:"and",

    group:[{

    rule:[],

    operate:"greater",

    group:[]

    }]

    }

    数据库存储规则结构的JSON串,合并时将JSON串反序列化之后使用and与系统计算得出规则对象连接即可,合并后的规则结构解析成简单SQL语句已经不是很难了。

    但是对于多表联合查询时应该如何处理呢?

    解析成SQL语句时可以使用表名+字段名的方式,可是遇到查询中使用别名的时候,这种方式也不能正常工作,这里暂时的处理方式是支持解析时传递别名。

    一个用户拥有多个角色,不同角色对于同一个规则设置不同的值应该如何处理?

    譬如,用户A拥有角色role1、role2,其中:

    role1适用规则:[销售地区] [等于] [上海]role2适用规则:[销售地区] [等于] [北京]

    那么用户A合并后的数据规则应该是:

    用户A适用规则:([销售地区] [等于] [上海]) or ([销售地区] [等于] [北京])

    即:一个用户对于同一个规则元的多个规则设置,应使用or连接后再与其他规则元进行and连接。

    2.5、确定当前查询适用的数据规则

    经过上述的规则配置与解析之后,我们很容易拿到当前用户适用的数据规则集合。但是在一次查询时我们应该使用集合中哪些规则进行过滤呢?一次查询是否开启数据规则过滤,使用哪些表的规则过滤应该是开发者来决定,类似:

    xxxQuery(...).withDataRule("`table1`,`table2`");

    即表示当前用户本次查询使用table1、table2中配置的数据规则。数据表中的每条规则应该支持在管理后台设置是否启用,这样理论上可实现每个用户对每一条数据规则的配置。

    欢迎工作一到八年的Java工程师朋友们加入Java高级交流:787707172

    本群提供免费的学习指导 架构资料 以及免费的解答

    不懂得问题都可以在本群提出来 之后还会有直播平台和讲师直接交流噢

    展开全文
  • 从零开始java数据权限篇:数据权限

    千次阅读 2019-09-09 11:59:05
    一:数据权限的产生 二:数据权限的数据切割 1.数据对应的层级图 2.用户数据查询 3.用户流程管理 4.部门-岗位-公司查询拓扑图 三.说明 一:数据权限的产生 在一个后管系统中,由2个最重要的权限划分。第一...

    目录

    一:数据权限的产生

    二:数据权限的数据切割

    1.数据对应的层级图

    2.用户数据查询

    3.用户流程管理

    4.部门-岗位-公司查询拓扑图

    三.说明



    一:数据权限的产生

      在一个后管系统中,由2个最重要的权限划分。第一个访问权限,通过控制访问路径、请求来控制访问的权限,第二个是数据权限,通过一系列的分割策略来对用户进行管理。

     访问权限,可以这么说访问权限可以通过集成框架(比如shiro或者Spring seurity或者oauth2)来控制。所以严格意义上来说访问权限是一个技术框架

     数据权限,数据权限用来对用户信息进行管理包括但是不限于用户列表查询、子母公司数据划分以及工作流等的一系列。但是一个比较尴尬的问题就是,数据权限实则是一个业务层,它是由具体的公司业务来决定的。当然,凡是可以抽象出来的我们都可以抽象出来。目前主流的的数据权限控制以 部门-岗位-上级-本级 这样一个递减结构来做通用式的管理。

      也翻看了一些所谓的数据级权限中间件比如目前介绍较多的Ralasafe,实则上就是在访问Sql上做一些侵入式Sql限制。基于此,我们完全可以在项目中使用AOP做。但是很尴尬的是目前这个数据级的中间件已经停止维护。

     

    二:数据权限的数据切割

      在上面,我们谈到一般都是以 部门-岗位-上级-本级 的结构来做数据权限控制。但是一般项目中会把上级-本级合并产生一个简单的结构:部门-岗位-用户 来维护数据权限。

    1.数据对应的层级图

                                                   

      首先:部门对于用户而言,是用户固有的一个属性,只能是一对一关系。

                  岗位对于用户而言 ,是由上级或者管理员分配的一个属性,可以是一对多(即一个用户被指向多个部门的负责人,但是

                                                        他本身只有某一个部门的属性)。

                  部门对岗位而言,同时也是一对多的关系,即一个部门有多个角色。

    2.用户数据查询

                   

     然后,我们针对组织结构细分:

     (1)公司结构以及部门结构:管理层级(具有本部门的最高权限)以及一般员工(只有自我查询权限)

                                                           其中总公司的管理层具有整个结构的最高权限。

    (2)单个单位中的结构:单个单位内的负责人具有最高单位权限

                                                  单个单位的次级负责人具有管理名下的权限。(即上级-下级的管理权限)

                                                   单个单位内的普通员工只具有自身的权限

    3.用户流程管理

      由于后期要整合工作流,因此这里我们将流程梳理清晰。

     

    4.部门-岗位-公司查询拓扑图

    目前,主要角色分为以下几个:

    (1)普通员工(仅能看到自己的信息)

    (2)部门管理人(仅能看到本部门)

    (3)分公司董事(除了董事会部门的其他分公司所有的部门)

    (4)分公司总负责人(看到分公司所有的信息)

    (5)总公司董事会(除了总公司董事会之外的所有总,自公司的所有部门)

    (6)总董事

    部门-岗位-董事表拓扑(比较简单,就不做UML了)

    据图查询:

    (1)普通员工:这个就不用说了

    (2)部门负责人:查询 用户表中 部门id一样的即可

    (3)分公司董事会:第一步,根据用户表中的部门id去查询部门表

                                          第二步:根据部门表中的所有上级id,取出公司统一的开头(一般第一位为总公司id,第二位为

                                                          分公司id,第三位包括以后是部门层级一级一级玩下的id,以逗号隔开)

                                         第三步:根据公司统一的开头曲模糊查询所有的部门id(这里在程序中控制得当,是完全可以的)

                                         第四步:根据上一步的部门id,同时去掉本身的部门id(即董事会部门的id),然后反查用户表

     (4)分公司负责人 :在上一个的第四步,不去掉本身的部门id

     (5)总公司董事会:查询所有的,除了自身的部门id

    (6)总公司负责人:全查

    (7)自定义:选择部门,然后插入  岗位-部门表做联查(对,这个表只在这里用到)

    三.说明

      上面所描述的是通常情况下,一般都是根据具体业务做管理

     

     

    展开全文
  • 权限控制主要分为两块,认证(Authentication)与授权(Authorization)。认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Based Access Control)。RBAC包含为下面四个要素:用户、...

    权限控制主要分为两块,认证(Authentication)与授权(Authorization)。认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Based Access Control)。RBAC包含为下面四个要素:用户、角色、权限、资源。用户是源头,资源是目标,用户绑定至角色,资源与权限关联,最终将角色与权限关联,就形成了比较完整灵活的权限控制模型。

    资源是最终需要控制的标的物,但是我们在一个业务系统中要将哪些元素作为待控制的资源呢?我将系统中待控制的资源分为三类:

    URL访问资源(接口以及网页)

    界面元素资源(增删改查导入导出的按钮,重要的业务数据展示与否等)

    数据资源

    现在业内普遍的实现方案实际上很粗放,就是单纯的“菜单控制”,通过菜单显示与否来达到控制权限的目的。

    我仔细分析过,现在大家做的平台分为To C和To B两种:

    To C一般不会有太多的复杂权限控制,甚至大部分连菜单控制都不用,全部都可以访问。

    To B一般都不是开放的,只要做好认证关口,能够进入系统的只有内部员工。大部分企业内部的员工互联网知识有限,而且作为内部员工不敢对系统进行破坏性的尝试。

    所以针对现在的情况,考虑成本与产出,大部分设计者也不愿意在权限上进行太多的研发力量。

    菜单和界面元素一般都是由前端编码配合存储数据实现,URL访问资源的控制也有一些框架比如SpringSecurity,Shiro。

    目前我还没有找到过数据权限控制的框架或者方法,所以自己整理了一份。

    数据权限控制原理

    数据权限控制最终的效果是会要求在同一个数据请求方法中,根据不同的权限返回不同的数据集,而且无需并且不能由研发编码控制。这样大家的第一想法应该就是AOP,拦截所有的底层方法,加入过滤条件。这样的方式兼容性较强,但是复杂程度也会更高。我们这套系统中,采用的是利用Mybatis的plugin机制,在底层SQL解析时替换增加过滤条件。

    这样一套控制机制存在很明显的优缺点,首先缺点:

    适用性有限,基于底层的Mybatis。

    方言有限,针对了某种数据库(我们使用Mysql),而且由于需要在底层解析处理条件所以有可能造成不同的数据库不能兼容。当然Redis和NoSQL也无法限制。

    当然,假如你现在就用Mybatis,而且数据库使用的是Mysql,这方面就没有太大影响了。

    接下来说说优点:

    减少了接口数量及接口复杂度。原本针对不同的角色,可能会区分不同的接口或者在接口实现时利用流程控制逻辑来区分不同的条件。有了数据权限控制,代码中只用写基本逻辑,权限过滤由底层机制自动处理。

    提高了数据权限控制的灵活性。例如原本只有主管能查本部门下组织架构/订单数据,现在新增助理角色,能够查询本部门下组织架构,不能查询订单。这样的话普通的写法就需要调整逻辑控制,使用数据权限控制的话,直接修改配置就好。

    数据权限实现

    上一节就提及了实现原理,是基于Mybatis的plugins(查看官方文档)实现。

    MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

    ParameterHandler (getParameterObject, setParameters)

    ResultSetHandler (handleResultSets, handleOutputParameters)

    StatementHandler (prepare, parameterize, batch, update, query)

    Mybatis的插件机制目前比较出名的实现应该就是PageHelper项目了,在做这个实现的时候也参考了PageHelper项目的实现方式。所以权限控制插件的类命名为PermissionHelper。

    机制是依托于Mybatis的plugins机制,实际SQL处理的时候基于jsqlparser这个包。

    设计中包含两个类,一个是保存角色与权限的实体类命名为PermissionRule,一个是根据实体变更底层SQL语句的主体方法类PermissionHelper。

    首先来看下PermissionRule的结构:

    public class PermissionRule {

    private static final Log log = LogFactory.getLog(PermissionRule.class);

    /**

    * codeName

    * 适用角色列表

    * 格式如: ,RoleA,RoleB,

    */

    private String roles;

    /**

    * codeValue

    * 主实体,多表联合

    * 格式如: ,SystemCode,User,

    */

    private String fromEntity;

    /**

    * codeDesc

    * 过滤表达式字段,

    * {uid}会自动替换为当前用户的userId

    * {me} main entity 主实体名称

    * {me.a} main entity alias 主实体别名

    * 格式如:

    *

    *

    userId = {uid}

    *

    (userId = {uid} AND authType > 3)

    *

    ((userId = {uid} AND authType) > 3 OR (dept in (select dept from depts where manager.id = {uid})))

    *

    */

    private String exps;

    /**

    * codeShowName

    * 规则说明

    */

    private String ruleComment;

    }

    看完这个结构,基本能够理解设计的思路了。数据结构中保存如下几个字段:

    角色列表:需要使用此规则的角色,可以多个,使用英文逗号隔开。

    实体列表:对应的规则应用的实体(这里指的是表结构中的表名,可能你的实体是驼峰而数据库是蛇形,所以这里要放蛇形那个),可以多个,使用英文逗号隔开。

    表达式:表达式就是数据权限控制的核心了。简单的说这里的表达式就是一段SQL语句,其中设置了一些可替换值,底层会用对应运行时的变量替换对应内容,从而达到增加条件的效果。

    规则说明:单纯的一个说明字段。

    核心流程

    系统启动时,首先从数据库加载出所有的规则。底层利用插件机制来拦截所有的查询语句,进入查询拦截方法后,首先根据当前用户的权限列表筛选出PermissionRule列表,然后循环列表中的规则,对语句中符合实体列表的表进行条件增加,最终生成处理后的SQL语句,退出拦截器,Mybatis执行处理后SQL并返回结果。

    讲完PermissionRule,再来看看PermissionHelper,首先是头:

    @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),

    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})

    public class PermissionHelper implements Interceptor {

    }

    头部只是标准的Mybatis拦截器写法,注解中的Signature决定了你的代码对哪些方法拦截,update实际上针对修改(Update)、删除(Delete)生效,query是对查询(Select)生效。

    下面给出针对Select注入查询条件限制的完整代码:

    private String processSelectSql(String sql, List rules, UserDefaultZimpl principal) {

    try {

    String replaceSql = null;

    Select select = (Select) CCJSqlParserUtil.parse(sql);

    PlainSelect selectBody = (PlainSelect) select.getSelectBody();

    String mainTable = null;

    if (selectBody.getFromItem() instanceof Table) {

    mainTable = ((Table) selectBody.getFromItem()).getName().replace("`", "");

    } else if (selectBody.getFromItem() instanceof SubSelect) {

    replaceSql = processSelectSql(((SubSelect) selectBody.getFromItem()).getSelectBody().toString(), rules, principal);

    }

    if (!ValidUtil.isEmpty(replaceSql)) {

    sql = sql.replace(((SubSelect) selectBody.getFromItem()).getSelectBody().toString(), replaceSql);

    }

    String mainTableAlias = mainTable;

    try {

    mainTableAlias = selectBody.getFromItem().getAlias().getName();

    } catch (Exception e) {

    log.debug("当前sql中, " + mainTable + " 没有设置别名");

    }

    String condExpr = null;

    PermissionRule realRuls = null;

    for (PermissionRule rule :

    rules) {

    for (Object roleStr :

    principal.getRoles()) {

    if (rule.getRoles().indexOf("," + roleStr + ",") != -1) {

    if (rule.getFromEntity().indexOf("," + mainTable + ",") != -1) {

    // 若主表匹配规则主体,则直接使用本规则

    realRuls = rule;

    condExpr = rule.getExps().replace("{uid}", UserDefaultUtil.getUserId().toString()).replace("{bid}", UserDefaultUtil.getBusinessId().toString()).replace("{me}", mainTable).replace("{me.a}", mainTableAlias);

    if (selectBody.getWhere() == null) {

    selectBody.setWhere(CCJSqlParserUtil.parseCondExpression(condExpr));

    } else {

    AndExpression and = new AndExpression(selectBody.getWhere(), CCJSqlParserUtil.parseCondExpression(condExpr));

    selectBody.setWhere(and);

    }

    }

    try {

    String joinTable = null;

    String joinTableAlias = null;

    for (Join j :

    selectBody.getJoins()) {

    if (rule.getFromEntity().indexOf("," + ((Table) j.getRightItem()).getName() + ",") != -1) {

    // 当主表不能匹配时,匹配所有join,使用符合条件的第一个表的规则。

    realRuls = rule;

    joinTable = ((Table) j.getRightItem()).getName();

    joinTableAlias = j.getRightItem().getAlias().getName();

    condExpr = rule.getExps().replace("{uid}", UserDefaultUtil.getUserId().toString()).replace("{bid}", UserDefaultUtil.getBusinessId().toString()).replace("{me}", joinTable).replace("{me.a}", joinTableAlias);

    if (j.getOnExpression() == null) {

    j.setOnExpression(CCJSqlParserUtil.parseCondExpression(condExpr));

    } else {

    AndExpression and = new AndExpression(j.getOnExpression(), CCJSqlParserUtil.parseCondExpression(condExpr));

    j.setOnExpression(and);

    }

    }

    }

    } catch (Exception e) {

    log.debug("当前sql没有join的部分!");

    }

    }

    }

    }

    if (realRuls == null) return sql; // 没有合适规则直接退出。

    if (sql.indexOf("limit ?,?") != -1 && select.toString().indexOf("LIMIT ? OFFSET ?") != -1) {

    sql = select.toString().replace("LIMIT ? OFFSET ?", "limit ?,?");

    } else {

    sql = select.toString();

    }

    } catch (JSQLParserException e) {

    log.error("change sql error .", e);

    }

    return sql;

    }

    重点思路

    重点其实就在于Sql的解析和条件注入,使用开源项目JSqlParser。

    解析出MainTable和JoinTable。from之后跟着的称为MainTable,join之后跟着的称为JoinTable。这两个就是我们PermissionRule需要匹配的表名,PermissionRule::fromEntity字段。

    解析出MainTable的where和JoinTable的on后面的条件。使用and连接原本的条件和待注入的条件,PermissionRule::exps字段。

    使用当前登录的用户信息(放在缓存中),替换条件表达式中的值。

    某些情况需要忽略权限,可以考虑使用ThreadLocal(单机)/Redis(集群)来控制。

    结束语

    想要达到无感知的数据权限控制,只有机制控制这么一条路。本文选择的是通过底层拦截Sql语句,并且针对对应表注入条件语句这么一种做法。应该是非常经济的做法,只是基于文本处理,不会给系统带来太大的负担,而且能够达到理想中的效果。大家也可以提出其他的见解和思路。

    展开全文
  • 前言权限控制主要分为两块,认证(Authentication)与授权(Authorization)。认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Based Access Control)。RBAC包含为下面四个要素:...
  • JAVA 数据权限设计

    2015-09-20 08:05:00
    数据权限设计 自序 在各种系统中。要保证数据对象的安全性以及易操作性,使企业的各业务部门、职能部门可以方便并且高效的协同工作,那么一个好的数据权限管理设计就成为一个关键的问题。尽管企业中各个单元的...
  • 本文为我们介绍了功能权限和数据权限的不同点、以及不同部分中的要点与注意事项。做2B的系统总是不可回避的遇上权限问题,他不是核心业务却又必不可少,而且总是牵一发而动全身,更要命的是不同客户组织架构完全不同...
  • 1 数据库实现权限操作既是管理又是超级管理员的需要五表关联1.1 四表联动1.2 表设计models.py# 权限系统四标关联:角色表、用户表、节点表、权限表# 角色表class RoleModel(models.Model):name = models.CharField...
  • 数据权限,一般就是作用于查询,规定用户能看到哪些数据。本次案例以通用的 本人,本部门,本公司,全部 来做,具体可以根据自己业务来灵活处理。 一.数据权限要设置到角色上(但要注意,一个用户可能是有多个角色...
  • 向大家介绍一种很不错,也是...理论上可以有N个操作,这取决于你用于储存用户权限值的数据类型了。这样,如果用户有权限:添加A—2;删除B—3;修改B—4那用户的权限值 purview =2^2+2^3+2^4=28,也就是2的权的和了化...
  • 一、前言几乎在任何一个系统中,都离不开权限的设计,权限设计 = 功能权限 + 数据权限,而功能权限,在业界常常是基于RBAC(Role-Based Access Control)的一套方案。而数据权限,则根据不同的业务场景,则权限却不尽...
  • Transform数据权限浅析2之利用Java完成权限设置 一:项目背景 1.1:cognos的两种建模工具 为了更好的满足客户的需求,提升报表展现的效率,一种建模工具已经不能满足报表开发和展现的需要。Cognos...
  • 相信各位读者对于角色权限管理这个需求并不陌生。那么是怎么实现的呢?今天小编来说道说道!1、首先我们来进行数据库的设计,如何设计数据库是实现权限控制的关键:1)用户表:id:主键、自增、intname:用户名 、...
  • 最近在研究数据级别的权限过滤,但是试了好多种方式,自己想想感觉又行不通,有做过这方面的大神么,一块讨论下,给点思路。 首先说下要实现的功能:(列字段暂不考虑) 其实说白了就两个问题, 第一,如何配置 ...
  • 主要介绍了Java如何利用Mybatis进行数据权限控制详解,数据权限控制最终的效果是会要求在同一个数据请求方法中,根据不同的权限返回不同的数据集,而且无需并且不能由研发编码控制。,需要的朋友可以参考下
  • 首先,ehcache是一个纯java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。第一步、导入ehcache的jar包net.sf.ehcacheehcache-core2.6.6第二步、复制ehcache配置文件到项目中...
  • Java利用Mybatis进行数据权限控制

    千次阅读 2019-06-06 18:19:18
    权限控制主要分为两块,认证(Authentication)与授权(Authorization)。认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Based Access Control)。RBAC包含为下面四个要素:...
  • 控制一个用户能访问哪些资源我们有很成熟的权限管理模型即RBAC,但是控制用户只能访问某部分资源(即我们常说的数据权限)使用RBAC模型是不够的,本文我们尝试在RBAC模型的基础上融入数据权限的管理控制。首先让我们先...
  • ‍权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少。...从控制力度来看,可以将权限...2,向系统提交数据,比如删除订单、修改客户资料数据权限是在功能权...
  • 吐槽:刚在同事的帮忙下,把maven工程成功导入到eclipse,期间遇到的最大问题就是安装eclipse插件,花费了其中大部分的时间现在做的研发产品,遇到的一个新的需求是“控制外部系统对于表中字段的访问权限”,其实...
  • 一.Java访问权限饰词(access specifiers)Java有public、protect、friendly、private四种访问权限,并且这四访问权限的访问范围越来越小。1. friendly1) 果一个class内的数据成员或方法没有任何权限饰词,那么它的...
  • java相关:SpringMVC 数据校验方法(必看篇)发布于 2020-7-27|复制链接下面小妖就为大家带来一篇SpringMVC 数据校验方法(必看篇)。小妖觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小妖过来看看吧...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,931
精华内容 2,372
关键字:

java数据权限

java 订阅