版权说明: 本文由博主keep丶原创,转载请注明出处。
原文地址: https://blog.csdn.net/qq_38688267/article/details/114134664
众所周知,系统权限
分为功能权限
和数据权限
,功能权限
决定用户能访问哪些界面有哪些功能,数据权限
则决定用户能看到哪些数据。绝大部分系统都会用到这个功能,可以说是非常常见了。
数据权限限制该怎么做呢? 一般情况下大家都是在需要限制的sql中加上该用户的权限条件,作者之前也是这么实现的。
虽然当时把这些权限条件SQL都封装了,每次使用的时候只要引用就行,但是还是感觉很麻烦,就想着能不能让程序来帮我实现这些数据权限限制SQL的注入呢?(我可真是个喜欢"偷懒"的小聪明呢)
有了这个想法,作者就开始捣鼓了,经过一阵捣鼓后,作者封装的基于Mybatis的 通用数据权限限制SQL工具V1.0
版本总算捣鼓出来了!
看完上面的效果,各位是不是很奇怪? sys_role
这张表怎么来的?df_als
别名怎么来的?ON sr.tenant_id = df_als.tenant_id
怎么来的?sr.id IN ('asdf', 'asdff')
又是怎么来的?
全都是配置出来的! 为了足够通用,很多东西都是可配的,如:
还有更多:
要实现这个功能,有几个要点:
下面一一为大家介绍作者的实现方式。
标记很简单,用个注解就好了。获取作者利用了mybatis的mapper扫描工作,重写其方法让其顺便帮我们检查下是否带有标记注解。
/**
* 数据权限限制SqlInjector
*
* 重写mp的mapper扫描逻辑
* 在扫描mapper后检查是否需要数据权限限制并缓存这些信息
*
* @author zzf
* @date 2021/2/5 15:02
*/
@Slf4j
public class DataAuthSqlInjector extends DefaultSqlInjector {
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
String resource = ResourceUtils.getResourceKey(mapperClass);
Set<String> needAuthMethodSet = new HashSet<>();
Set<String> notNeedAuthMethodSet = new HashSet<>();
if (mapperClass.isAnnotationPresent(DataAuth.class)) {
DataAuthCache.addNeedAuthResource(resource);
for (Method method : mapperClass.getMethods()) {
needAuthMethodSet.add(method.getName());
}
}
for (Method method : mapperClass.getMethods()) {
if (method.isAnnotationPresent(DataAuth.class)) {
needAuthMethodSet.add(method.getName());
}
if (method.isAnnotationPresent(DataAuthIgnore.class)) {
notNeedAuthMethodSet.add(method.getName());
}
}
if (needAuthMethodSet.size() > 0) {
DataAuthCache.addNeedAuthMethod(resource, needAuthMethodSet);
DataAuthCache.addResource2EntityClassMap(resource, extractModelClass(mapperClass));
}
if (notNeedAuthMethodSet.size() > 0) {
DataAuthCache.addNotNeedAuthMethod(resource, notNeedAuthMethodSet);
}
super.inspectInject(builderAssistant, mapperClass);
}
}
拦截也比较简单,我们直接实现Mybatis Plus提供的拦截接口:
/**
* 数据权限拦截器
*
* @author zzf
* @date 11:05 2021/2/4
*/
@Slf4j
public class DataAuthInterceptor implements InnerInterceptor {
//限制策略
private final DataAuthStrategy dataAuthStrategy;
private final Set<String> needAuthId = new TreeSet<>();
private final Set<String> notNeedAuthId = new TreeSet<>();
public DataAuthInterceptor(DataAuthStrategy dataAuthStrategy) {
this.dataAuthStrategy = dataAuthStrategy;
}
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
String id = ms.getId();
String resource = ResourceUtils.getResourceKey(ms.getResource());
String methodName;
if (id.contains(".")) {
methodName = id.substring(id.lastIndexOf(".") + 1);
} else {
methodName = id;
}
if (needAuthCheck(resource, methodName, id)) {
log.warn("sql before parsed: " + boundSql.getSql());
Class<?> entityClass = DataAuthCache.getEntityClassByResource(resource);
try {
Select selectStatement = (Select) CCJSqlParserUtil.parse(boundSql.getSql());
dataAuthStrategy.doParse(selectStatement, entityClass);
ReflectUtil.setFieldValue(boundSql, "sql", selectStatement.toString());
log.warn("sql after parsed: " + boundSql.getSql());
} catch (JSQLParserException e) {
throw new SQLException("sql role limit fail: sql parse fail.");
}
}
}
/**
* 判断是否需要数据权限限制
*/
private boolean needAuthCheck(String resource, String methodName, String id) {
if (needAuthId.contains(id)) {
return true;
}
if (notNeedAuthId.contains(id)) {
return false;
}
boolean result;
if (DataAuthCache.isNeedAuthResource(resource)) {
result = !DataAuthCache.isNotNeedAuthMethod(resource, methodName);
} else {
result = DataAuthCache.isNeedAuthMethod(resource, methodName);
}
if (result) {
needAuthId.add(id);
} else {
notNeedAuthId.add(id);
}
return result;
}
}
自己手写SQL修改是不太现实的,要考虑的情况太多了,作者使用JSqlParser
工具来进行SQL修改,部分代码如下:
/**
* 进行数据权限限制的SQL处理
*/
public SqlHandlerResult doParse(Select selectStatement, Class<?> entityClass) {
T data = dataGetter.getData();
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
//判断数据是否为空,且为空时是否处理
if (data == null) {
if (parseIfDataAbsent()) {
//空数据权限处理
EqualsTo equalsTo = new EqualsTo();
equalsTo.setRightExpression(new LongValue(1));
equalsTo.setLeftExpression(new LongValue(0));
//在where子句中添加 1 = 0 条件, 并 and 原有条件
AndExpression andExpression = new AndExpression(equalsTo, selectBody.getWhere());
selectBody.setWhere(andExpression);
}
return SqlHandlerResult.nonData(parseIfDataAbsent());
} else {
if (dataIsFull(data)) {
// 拥有全部权限,此处不对SQL做任何处理
return SqlHandlerResult.hadAll();
} else {
// 正常处理
Table fromItem = (Table) selectBody.getFromItem();
aliasHandle(selectBody, fromItem);
joinHandle(entityClass, selectBody, fromItem);
whereHandle(selectBody);
}
}
return SqlHandlerResult.handle();
}
/**
* 别名处理
* 如果from的表没有别名,手动给他加个别名,并给所有 查询列和 where条件中的列增加别名前缀
*/
public void aliasHandle(PlainSelect selectBody, Table fromItem) {
if (fromItem.getAlias() == null || fromItem.getAlias().getName() == null) {
fromItem.setAlias(new Alias(DataAuthConstants.DEFAULT_ALIAS));
selectBody.getSelectItems().forEach(s ->
s.accept(new ExpressionVisitorAdapter() {
@Override
public void visit(Column column) {
column.setTable(fromItem);
}
})
);
Expression where = selectBody.getWhere();
if (where != null) {
where.accept(new ExpressionVisitorAdapter() {
@Override
public void visit(Column column) {
column.setTable(fromItem);
}
});
}
}
}
/**
* JOIN子句处理
*
* 将数据限制表通过JOIN的方式加入
*/
public void joinHandle(Class<?> entityClass, PlainSelect selectBody, Table fromItem) {
// 假如要实现 LEFT JOIN t2 ON t2.id2 = t1.id1
Table limitTable = getLimitTable();
Join join = new Join();// JOIN
join.setLeft(true);// LEFT JOIN
join.setRightItem(limitTable);// LEFT JOIN t2
EqualsTo equalsTo = new EqualsTo();// =
Column limitRelationColumn = new Column(relationColumnName);// id2
limitRelationColumn.setTable(limitTable);// t2.id2
Column targetRelationColumn = new Column(DataAuthCache.getFieldName(getId(), entityClass));// id1
targetRelationColumn.setTable(fromItem);// t1.id1
equalsTo.setLeftExpression(limitRelationColumn);
equalsTo.setRightExpression(targetRelationColumn);// t2.id2 = t1.id1
join.setOnExpression(equalsTo);// LEFT JOIN t2 ON t2.id2 = t1.id1
List<Join> joins = selectBody.getJoins();
if (joins == null) {
selectBody.setJoins(Collections.singletonList(join));
} else {
joins.add(join);
}
}
/**
* WHERE子句处理
*
* 添加数据权限条件
*/
public void whereHandle(PlainSelect selectBody) {
Column whereColumn = new Column(whereColumnName);
whereColumn.setTable(getLimitTable());
setOperatorValue();
AndExpression andExpression = new AndExpression(operator, selectBody.getWhere());
selectBody.setWhere(andExpression);
}
protected Table getLimitTable() {
Table limitTable = new Table(this.tableName);
limitTable.setAlias(new Alias(this.tableAlias));
return limitTable;
}
项目地址:https://gitee.com/zengzefeng/easy-frame
该功能只是初版,肯定还有很多不足和考虑不周的地方,还请各位大佬多多指点。后续还会继续优化迭代,希望能打造成一个spring-boot-starter,哈哈。