通用数据权限的设计思路

接着上个襄阳项目的需要, 目前的项目情况是,一期已经把功能权限做完了,可以对不同用户的不同权限功能做到限制,现在需要做数据的权限,不同的用户看到不同的数据。

根据目前的调研情况,有两种数据级别权限设计思路,都可以实现对人员访问的数据权限控制,从而实现不同的人员能够看到不同的数据,例如经理能够看到其部门下所有人的数据,而单个的员工只能看到自己的数据。用户拥有的权限越大,能看到的数据就越多。

第一种是使用先在网关进行权限的判断,此用户有这个操作的权限,再进行查询,查询时根据参数进行SQL的拼接。网关鉴定是否能访问以及转发请求,对SQL的拼接和处理在各微服务自己内部进行,服务A/B/C都需要自己写需要查询的参数。如下图所示

使用此方式存在一些弊端 ,使用SQL拼接的方式不利于统一管理权限,每个项目有自己的过滤方式,拼接SQL的方式,不利于统一管理,也不方便后期可能出现的规则修改,因为已经硬编码到代码中,需要各个服务自己修改一遍。

优点很明显,足够很简单,只需要根据相应的规则调整SQL就可以了。

 

第二种方式,专门抽离一个权限鉴定的服务,所有到达网关的请求后,网关去调用权限鉴定服务,进行访问的URL的权限判断,如果鉴定成功,则能够访问,网关转到请求到对应的服务,否则网关直接返回,提示用户没有权限访问。数据权限的控制依然需要各个微服务硬编码到各自的项目代码中去。如下图所示

 

目前没有什么方式能做这种通用的数据级权限控制(因为涉及到各个服务的查询SQL进行过滤,拼接,在这个权限鉴定服务中没法实现这样的功能,对各种不同的查询的SQL进行拼接,过滤)。

 

这种方式相对于第一种把对接口的访问权限校验抽离出来了,减轻了网关的压力,依然是各个服务自己实现数据查询的控制。

 

现在的思路是: 

新建一个mybatis的插件, 让各个服务都依赖这个(java项目可以继承, 其他的php得服务暂时不考虑), 这个插件的作用就是对

SQL进行过滤, 拼接. 鉴权服务会对配置的数据的权限进行读取, 形成一个SQL, 以传递参数的形式, 返回给网关, 网关转发给其他的微服务, 服务获取到携带的SQL参数, 利用插件对SQL进行拼接, 过滤, 就可以查询出来数据了.进行数据的返回.

 

下面是目前的一部分代码, 希望能够给各位一些启示:

package co.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.util.Properties;

/**
 * @author zhangke
 * @time 2019年12月23日10:10:35
 */
@Component
@Slf4j
@Intercepts({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
        @Signature(type = Executor.class,//这是指拦截哪个接口
                method = "query",//这个接口内的哪个方法名,不要拼错了
                //这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class AuthorityFilterPlugin implements Interceptor {

    /**
     * 这里是每次执行操作的时候,都会进行这个拦截器的方法内
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //对sql进行处理
        Object[] queryArgs = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) queryArgs[0];
        Object parameter = queryArgs[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        // 获取到SQL ,进行调整
        String sql = boundSql.getSql();
        if (!sql.contains("limit")) {
            sql = sql + "limit 1";
        }
        log.info("正在执行的sql是: {}", sql);
        return invocation.proceed();
    }

    /**
     * 主要是为了把这个拦截器生成一个代理放到拦截器链中
     */
    @Override
    public Object plugin(Object target) {
        //官方推荐写法
        return Plugin.wrap(target, this);
    }


    /**
     * 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
    }


}

 

下面是过滤的参数的设置.

package com.curefun.authority.filter;


import lombok.Data;
import java.util.List;

/**
 * 过滤器的参数实体类,用于拼接SQL用的
 *
 * question:
 * 1.分页怎么办? mybatis的分页插件之前执行...
 * 2.夺标关联查询怎么办? 别名的设置?
 * 3.这是单独的一个服务,获取到的sql, 可以用拼接url的形式转发给其他服务,其他服务又需要改造,添加mybatis 的插件.
4.一个查询方法对应的多个sql的时候,拼接哪一个?怎么指定?
 *
 */
@Data
public class FilterVO {


    private String id;
    /**
     * 服务名
     */
    private String serviceName;
    /**
     * 请求的url
     */
    private String requestUrl;
    /**
     * 类型 0:不启用   1:启用
     */
    private Integer filterState;
    /**
     * 参数的具体值
     */
    private List<ColumnDataVO> params;


}


package com.curefun.authority.filter;

import lombok.Data;


/**
 * 一个查询的所有要拼接的参数
 */
@Data
public class ColumnDataVO<T> {

    /**
     * 列名字
     */
    private String columnName;
    /**
     * 列值,String,Number,List,Set,类型,或者Pair的类型
     */
    private T value;
    /**
     * 类型
     */
    private OperationEnum operationEnum;
    /**
     * 参数类型
     */
    private ParamTypeEnum paramEnum;
    /**
     * 排序,从0 开始,数字越小拼接就越靠前,不能为空
     */
    private int orderNum;


    public ColumnDataVO(String columnName, T value, OperationEnum operationEnum, ParamTypeEnum paramEnum, int orderNum) {
        this.columnName = columnName;
        this.value = value;
        this.operationEnum = operationEnum;
        this.paramEnum = paramEnum;
        this.orderNum = orderNum;
    }

    public ColumnDataVO() {
    }

}


package com.curefun.authority.filter;

/**
 * 操作类型化的美剧
 */
public enum OperationEnum {
    EQUAL(1,"=","相等"),
    GT(2,">","大于"),
    LT(3,"<","小于"),
    GTE(4,">=","大于等于"),
    LTE(5,"<=","小于等于"),
    IN(6,"in","包含"),
    BETWEEN(7,"between","介于之间"),
    ;

    private int typeCode;
    private String operationSymbol;
    private String message;

    OperationEnum(int typeCode, String operationSymbol, String message) {
        this.typeCode = typeCode;
        this.operationSymbol = operationSymbol;
        this.message = message;
    }


    public int getTypeCode() {
        return typeCode;
    }

    public String getOperationSymbol() {
        return operationSymbol;
    }

    public String getMessage() {
        return message;
    }
}



package com.curefun.authority.filter;

/**
 * 参数类型
 */
public enum ParamTypeEnum {


    STRING(1,"字符串"),
    NUMBER(2,"数字"),
    LIST(3,"List"),
    SET(4,"Set"),
    /**
     * HH:mm:ss
     */
    TIME(5,"时间"),
    /**
     * yyyy-MM-dd HH:mm:ss
     */
    DATEtIME(6,"时间"),
    /**
     * 双值,用于between的前后范围
     */
    PAIR(7,"双值"),
    ;

    private int type;
    private String message;

    ParamTypeEnum(int type, String message) {
        this.type = type;
        this.message = message;
    }

    public int getType() {
        return type;
    }

    public String getMessage() {
        return message;
    }
}

 

未完成的部分:

1.根据FilterVO 做SQL的拼接.

2.以上的四个问题,有待于解决,

3.mybatis的过滤器中,怎么根据sql进行正确的拼接, 而且需要考虑效率问题.

5.系统配置的表设计,我这里没做,我想过一下, 但是没有把表设计出来,其实是个一对多的表设计, 两张表就够了.

 

好了, 就写到这里, 之所以不往下写了, 是因为业务暂时没有这方面的需求, 我这个只是属于前期的探索, 这个已经完成的项目, 还有很大的变数, 还未交付. 所以不做了. 好好复习.

这里放一个别人设计的一张表结构: 

CREATE TABLE `sys_acl_data` (
  `id` int(11) NOT NULL,
  `acl_id` int(11) NOT NULL COMMENT '对应权限表主键',
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:可用,0:不可用',
  `param` varchar(20) NOT NULL DEFAULT '' COMMENT '参数',
  `operation` int(11) NOT NULL DEFAULT '0' COMMENT '操作类型,0;等于,1:大于,2:小于,3:大于等于,4:小于等于,5:包含,6:介于之间,。。。',
  `value1` varchar(100) NOT NULL DEFAULT '0',
  `value2` varchar(100) NOT NULL DEFAULT '0',
  `next_param_op` int(11) NOT NULL DEFAULT '0' COMMENT '后续有参数时连接的关系,0:没有其他参数控制,1:与&&,2:或||',
  `seq` tinyint(4) NOT NULL DEFAULT '0' COMMENT '顺序',
  PRIMARY KEY (`id`),
  KEY `idx_acl_id` (`acl_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='数据权限表';

有可取之处, 不过要想在实际生产环境中适用, 还有很多的工作需要做, 

 

如果您觉得写得不多, 可以请作者喝一杯咖啡

 

 

 

  • 10
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
1、 抽象——总体思路。 先看这个ER图。 很简单,就是说明一下人员和资源的关系,一个人可以使用多个资源,一个资源可以被多个人使用,就是多对多的关系了。 不知道这个是不是可以叫做“抽象”。这个就是在金字塔的顶端来看权限了,站在顶端来看,就这么一点,估计没有那种情况可以逃出这个描述吧。 资源:这里指的资源是广义上的资源,包括很多的东东,模块、数据,菜单、节点、按钮、控件,表、字段、存储过程,页面、窗口、表单、图表、报表,什么都可以算作是一种资源。您也可以把您遇到的一些情况都来算作是一种资源。关于资源先说这些,下面还有详细的说明。 2、 加入权限 第一个图也太简单了,我们把他详细一下,把人员分成两个表——人员基本信息和登录信息,在加入“权限”。就是下面这个表了。 人员分成两个表可以应对很多的情况,比如一个人可以有多个登录帐号,人员基本信息还可以和其他的表相关联,登录方面的需求有什么变化的话,只需要修改登录信息表就可以了,不会影响人员基本信息表,不会让其越来越臃肿。 以前对于“权限”是很模糊的,似有似无的感觉,现在看来他其实就是一个多对多的关联表,呵呵。当然您可以说我的这个看法不对,呵呵,我只是说一下我的感觉。 3、 加入角色 第二个图,是把帐号的资源直接联系起来,这个有一个不方便的地方,比如有五个业务员他们的功能都是一样的,但是我们却需要做五遍一样的操作才能给这五个业务员设置好权限,而当业务员可以做的事情有变化的时候,我就又需要做五次相同的操作,这个就很麻烦了,所以引用了“角色”。 我们可以建立一个业务员角色,设置业务员角色可以做的事情,然后把五个业务员和业务员角色关联起来。这样就方便了,业务员可以做得事情有变化的时候,我只需要修改业务员角色可以做得事情就可以了。 您可能会问,客户的人少,每个人做得事情都不一样,这个怎么办呀? 这也好办呀,一个人一个角色就可以了。虽然对于这种情况多用了一个角色,有点绕远的感觉,但是总体来说是可以接受的。角色初期设置一下就可以了,角色和人员“绑定”之后,修改角色可以做什么事情,和修改人员可以做什么事情,操作步骤都是一样的。 您可能又问了,客户是一个很大的公司,设置了n个角色之后,客户提出了一个需求:张三这个人比较特殊,他可以做XX事情,但是有没有对应的角色,也不想再多设置一个角色了,需要直接给张三设置可以做这件事情就可以了。 这个又要怎么处理呢?是不是要修改表结构了呢?我是不想改的,还是用角色绑定的方法来处理,增加一个“张三专用角色”,这个角色是“隐藏”的,不和其他的角色一样的管理,需要通过对“张三”来管理。这个好像说不太清楚,先这样吧,呵呵。 4、 表关联图 我觉得ER图就是ER图,不能代替表关系图,所以我就又做了一个表关系图。 【图四】 左面从上往下看,人员、登录帐号、角色、资源,右面是两个多对多的关联表。这个看起来就比较清晰了吧。 这个设计还可以吧,资源保罗万象什么都可以往里放,您可以展开您的联想,帮想到的东东都放进去就可以了。 这个图从设计的角度来说应该是挺简洁的,五六个表就搞定了。而且也可以适合很大的范围,因为那个资源的定义实在是太广泛了,到了无所不包的程度了。但是这个设计真的好吗?或者是实用吗? .......

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值