通用数据权限的设计思路

半世苍凉 提交于 2020-01-15 10:23:56

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

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

第一种是使用先在网关进行权限的判断,此用户有这个操作的权限,再进行查询,查询时根据参数进行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='数据权限表';

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

 

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

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!