接着上个襄阳项目的需要, 目前的项目情况是,一期已经把功能权限做完了,可以对不同用户的不同权限功能做到限制,现在需要做数据的权限,不同的用户看到不同的数据。
根据目前的调研情况,有两种数据级别权限设计思路,都可以实现对人员访问的数据权限控制,从而实现不同的人员能够看到不同的数据,例如经理能够看到其部门下所有人的数据,而单个的员工只能看到自己的数据。用户拥有的权限越大,能看到的数据就越多。
第一种是使用先在网关进行权限的判断,此用户有这个操作的权限,再进行查询,查询时根据参数进行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='数据权限表';
有可取之处, 不过要想在实际生产环境中适用, 还有很多的工作需要做,
如果您觉得写得不多, 可以请作者喝一杯咖啡
来源:CSDN
作者:长河
链接:https://blog.csdn.net/u010398771/article/details/103662466