Compare commits
6 Commits
master
...
temp_auto_
Author | SHA1 | Date |
---|---|---|
zengzefeng | 0dcc637ece | |
zengzefeng | c1d213d1d8 | |
zengzefeng | 6023473bcd | |
zengzefeng | 696e739b0d | |
YunaiV | 45d192bcce | |
zengzefeng | 36146a5e71 |
|
@ -0,0 +1,12 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.annotion;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface DataAuth {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.annotion;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface DataAuthIgnore {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,280 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.getter.DataGetter;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthCache;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthConstants;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.SqlHandlerResult;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
|
||||||
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||||
|
import net.sf.jsqlparser.schema.Column;
|
||||||
|
import net.sf.jsqlparser.schema.Table;
|
||||||
|
import net.sf.jsqlparser.statement.select.Join;
|
||||||
|
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||||
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
|
import org.apache.ibatis.reflection.property.PropertyNamer;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于数据权限限制的处理器抽象类
|
||||||
|
* <p>
|
||||||
|
* 后续要需要考虑:
|
||||||
|
* 1、需要多张表实现限制的,如
|
||||||
|
* 需要JOIN sys_dept t1 xxx JOIN sys_user t2 ON t1.xx = t2.xx
|
||||||
|
* 然后在where中通过t2.xxx = xxx进行数据限制
|
||||||
|
* 2、无需join表的情况,即需要限制的表中直接包含指定字段,只需要加入WHERE条件即可
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 11:05 2021/2/4
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractDataAuthSqlHandler<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该表对应的实体类
|
||||||
|
* 如:
|
||||||
|
* sys_role
|
||||||
|
* sys_user
|
||||||
|
*/
|
||||||
|
protected final Class<?> entityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该表对应的实体类
|
||||||
|
*/
|
||||||
|
protected final String tableName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表别名
|
||||||
|
*/
|
||||||
|
protected final String tableAlias;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联字段名
|
||||||
|
*/
|
||||||
|
protected final String relationColumnName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* where字段名
|
||||||
|
*/
|
||||||
|
protected final String whereColumnName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据的对象
|
||||||
|
*/
|
||||||
|
protected final DataGetter<T> dataGetter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql中的表达式
|
||||||
|
*/
|
||||||
|
protected final Expression operator;
|
||||||
|
|
||||||
|
|
||||||
|
protected <R> AbstractDataAuthSqlHandler(Class<?> entityClass,
|
||||||
|
String tableAlias,
|
||||||
|
DataGetter<T> dataGetter,
|
||||||
|
SFunction<R, ?> relationFieldFunction,
|
||||||
|
SFunction<R, ?> whereFieldFunction,
|
||||||
|
Expression operator) {
|
||||||
|
this.entityClass = entityClass;
|
||||||
|
this.tableAlias = tableAlias;
|
||||||
|
this.dataGetter = dataGetter;
|
||||||
|
this.operator = operator;
|
||||||
|
|
||||||
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
|
||||||
|
Assert.notNull(tableInfo, DataAuthConstants.notExistsTableInfo(entityClass.getSimpleName()));
|
||||||
|
this.tableName = tableInfo.getTableName();
|
||||||
|
|
||||||
|
// 获取relationColumnName
|
||||||
|
String relationFieldName = PropertyNamer.methodToProperty(LambdaUtils.resolve(relationFieldFunction).getImplMethodName());
|
||||||
|
if (relationFieldName.equals(tableInfo.getKeyProperty())) {
|
||||||
|
this.relationColumnName = tableInfo.getKeyColumn();
|
||||||
|
} else {
|
||||||
|
Optional<String> columnNameOptional = tableInfo.getFieldList().stream()
|
||||||
|
.filter(s -> s.getProperty().equals(relationFieldName))
|
||||||
|
.map(TableFieldInfo::getColumn)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (columnNameOptional.isPresent()) {
|
||||||
|
this.relationColumnName = columnNameOptional.get();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(DataAuthConstants.notExistsFieldInfo(relationFieldName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取whereFieldName
|
||||||
|
String whereFieldName = PropertyNamer.methodToProperty(LambdaUtils.resolve(whereFieldFunction).getImplMethodName());
|
||||||
|
if (whereFieldName.equals(tableInfo.getKeyProperty())) {
|
||||||
|
this.whereColumnName = tableInfo.getKeyColumn();
|
||||||
|
} else {
|
||||||
|
Optional<String> whereFiledOptional = tableInfo.getFieldList().stream()
|
||||||
|
.filter(s -> s.getProperty().equals(whereFieldName))
|
||||||
|
.map(TableFieldInfo::getColumn)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (whereFiledOptional.isPresent()) {
|
||||||
|
this.whereColumnName = whereFiledOptional.get();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(DataAuthConstants.notExistsFieldInfo(whereFieldName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进行数据权限限制的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子句处理
|
||||||
|
* <p>
|
||||||
|
* 将数据限制表通过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子句处理
|
||||||
|
* <p>
|
||||||
|
* 添加数据权限条件
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SqlHandler唯一标识,用于{@link DataAuthStrategy 中使用}
|
||||||
|
*/
|
||||||
|
abstract public int getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果数据为空,是否处理SQL
|
||||||
|
* <p>
|
||||||
|
* 如果为true,则表示返回空数据,系统将增加 1 = 0条件
|
||||||
|
* 如果为false,则不改变sql
|
||||||
|
*/
|
||||||
|
protected abstract boolean parseIfDataAbsent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入数据限制条件的值
|
||||||
|
* {@link #operator} {@code operator.setRightExpression(getData())}
|
||||||
|
* 这个方法可以写成默认的,对所有类型进行遍历并实现所有类型的插入方法。
|
||||||
|
* 但是好麻烦。。。要考虑 =、 IN、 LIKE(三种) 等表达式,还有不同的数据类型balabala一大堆
|
||||||
|
*/
|
||||||
|
protected abstract void setOperatorValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断该数据是否属于全部权限数据
|
||||||
|
*
|
||||||
|
* @param data 数据权限数据
|
||||||
|
*/
|
||||||
|
protected abstract boolean dataIsFull(T data);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthCache;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthConstants;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.apache.http.util.Asserts;
|
||||||
|
import org.apache.ibatis.reflection.property.PropertyNamer;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加数据权限限制配置的辅助工具
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/4 15:35
|
||||||
|
*/
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class DataAuthCacheHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该表对应的实体类
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private Set<Class<?>> targetEntityClassSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制处理器
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private AbstractDataAuthSqlHandler<?> sqlHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象表 用于与数据权限限制的表关联 的字段名
|
||||||
|
*/
|
||||||
|
private String targetFieldName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存上面三个参数的对应关系map
|
||||||
|
* sqlHandlerId : targetClass : fieldName map
|
||||||
|
* <p>
|
||||||
|
* 用于在处理SQL时,根据对应关系修改SQL
|
||||||
|
*/
|
||||||
|
private final Map<AbstractDataAuthSqlHandler<?>, Map<Class<?>, String>> sqlHandler2TargetClass2FieldNameMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public DataAuthCacheHelper addTargetEntityClass(Class<?> targetEntityClass) {
|
||||||
|
if (this.targetEntityClassSet == null) {
|
||||||
|
this.targetEntityClassSet = new HashSet<>();
|
||||||
|
}
|
||||||
|
this.targetEntityClassSet.add(targetEntityClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataAuthCacheHelper addTargetEntityClass(Collection<Class<?>> targetEntityClassList) {
|
||||||
|
if (this.targetEntityClassSet == null) {
|
||||||
|
this.targetEntityClassSet = new HashSet<>();
|
||||||
|
}
|
||||||
|
this.targetEntityClassSet.addAll(targetEntityClassList);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> DataAuthCacheHelper setTargetFieldName(SFunction<T, ?> function) {
|
||||||
|
targetFieldName = PropertyNamer.methodToProperty(LambdaUtils.resolve(function).getImplMethodName());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定前三个参数的信息,生成 sqlHandler2TargetClass2FieldNameMap 数据
|
||||||
|
*/
|
||||||
|
public DataAuthCacheHelper bind() {
|
||||||
|
valueCheck();
|
||||||
|
Map<Class<?>, String> targetClass2FieldNameMap = new HashMap<>();
|
||||||
|
targetEntityClassSet.forEach(entityClass -> {
|
||||||
|
//直接从MP的缓存中获取实体类对应的表信息,查询实体类字段对应的表字段名
|
||||||
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
|
||||||
|
//TableInfo中 主键和其他字段分别存储,因此这里要分别判断。
|
||||||
|
if (targetFieldName.equals(tableInfo.getKeyProperty())) {
|
||||||
|
targetClass2FieldNameMap.put(entityClass, tableInfo.getKeyColumn());
|
||||||
|
} else {
|
||||||
|
Optional<String> relationColumnOptional = tableInfo.getFieldList().stream()
|
||||||
|
.filter(s -> s.getProperty().equals(targetFieldName))
|
||||||
|
.map(TableFieldInfo::getColumn)
|
||||||
|
.findFirst();
|
||||||
|
if (relationColumnOptional.isPresent()) {
|
||||||
|
targetClass2FieldNameMap.put(entityClass, relationColumnOptional.get());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(DataAuthConstants.notExistsFieldInfo(targetFieldName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sqlHandler2TargetClass2FieldNameMap.put(sqlHandler, targetClass2FieldNameMap);
|
||||||
|
reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前三个参数数据验证
|
||||||
|
*/
|
||||||
|
public void valueCheck() {
|
||||||
|
Asserts.notNull(this.targetEntityClassSet, "targetEntityClassList can not be null");
|
||||||
|
Asserts.notNull(this.sqlHandler, "sqlHandlerList can not be null");
|
||||||
|
Asserts.notNull(this.targetFieldName, "targetFieldName can not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置前三个参数的数据
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
targetEntityClassSet = null;
|
||||||
|
sqlHandler = null;
|
||||||
|
targetFieldName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入到缓存中
|
||||||
|
*/
|
||||||
|
public void cache() {
|
||||||
|
sqlHandler2TargetClass2FieldNameMap.forEach((key, val) -> {
|
||||||
|
DataAuthCache.addSqlHandler(key);
|
||||||
|
DataAuthCache.addSqlHandlerId2TargetClass2FieldNameMap(key.getId(), val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthCache;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.util.ResourceUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.sf.jsqlparser.JSQLParserException;
|
||||||
|
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||||
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
|
import org.apache.ibatis.executor.Executor;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
|
import org.apache.ibatis.session.RowBounds;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限拦截器
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.annotion.DataAuth;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.annotion.DataAuthIgnore;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthCache;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.util.ResourceUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制SqlInjector
|
||||||
|
* <p>
|
||||||
|
* 重写mp的mapper扫描逻辑
|
||||||
|
* 在扫描mapper后检查是否需要数据权限限制并缓存这些信息
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/5 15:02
|
||||||
|
*/
|
||||||
|
@Slf4j // TODO FROM 芋艿 to zzf:类名可能需要在想想哈 DONE
|
||||||
|
public class DataAuthSqlInjector extends DefaultSqlInjector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
|
||||||
|
String resource = ResourceUtils.getResourceKey(mapperClass);
|
||||||
|
|
||||||
|
// TODO FROM 芋艿 to zzf:复数 DONE
|
||||||
|
Set<String> needAuthMethodSet = new HashSet<>();
|
||||||
|
Set<String> notNeedAuthMethodSet = new HashSet<>();
|
||||||
|
|
||||||
|
if (mapperClass.isAnnotationPresent(DataAuth.class)) { // TODO FROM 芋艿 to zzf:可以考虑使用 isAnnotationPresent 方法,简洁 DONE
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component;
|
||||||
|
|
||||||
|
|
||||||
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制的处理策略
|
||||||
|
* 比如
|
||||||
|
* 先通过岗位限制,如果岗位不合适,再通过角色限制,再通过其他字段限制等等
|
||||||
|
*
|
||||||
|
* 每种限制方式应该有对应的{@link AbstractDataAuthSqlHandler}
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 17:54 2021/1/29
|
||||||
|
*/
|
||||||
|
public interface DataAuthStrategy {
|
||||||
|
|
||||||
|
void doParse(Select selectStatement, Class<?> entityClass);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component.getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据抽象对象
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/18 11:05
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface DataGetter<R> {
|
||||||
|
|
||||||
|
R getData();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component.getter;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过静态方法获取数据权限限制数据
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/18 11:05
|
||||||
|
*/
|
||||||
|
public class StaticDataGetter<R> implements DataGetter<R> {
|
||||||
|
|
||||||
|
private final Supplier<R> supplier;
|
||||||
|
|
||||||
|
public StaticDataGetter(Supplier<R> supplier) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R getData() {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.component.getter;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过执行对象方法获取数据权限限制数据
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/18 11:05
|
||||||
|
*/
|
||||||
|
public class TargetDataGetter<T, R> implements DataGetter<R> {
|
||||||
|
|
||||||
|
private final Function<T, R> function;
|
||||||
|
|
||||||
|
private final T target;
|
||||||
|
|
||||||
|
public TargetDataGetter(T target, Function<T, R> function) {
|
||||||
|
this.function = function;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R getData() {
|
||||||
|
return function.apply(target);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.entity;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.AbstractDataAuthSqlHandler;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制的缓存信息存储类
|
||||||
|
* TODO FROM 芋艿 to zzf:最好不要暴露原始的数据结构出去 DONE
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/5 15:02
|
||||||
|
*/
|
||||||
|
public class DataAuthCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要权限限制的resource集合 from {@link MappedStatement#getResource()}
|
||||||
|
*/
|
||||||
|
private final static Set<String> needAuthResourceSet = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要权限限制的 resource:方法名集合 map
|
||||||
|
*/
|
||||||
|
private final static Map<String, Set<String>> needAuthMethodMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不需要权限限制的 resource:方法名集合 的 map
|
||||||
|
*/
|
||||||
|
private final static Map<String, Set<String>> notNeedAuthMethodMap = new HashMap<>(32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resource:对应表实体类Class对象 map
|
||||||
|
*/
|
||||||
|
private final static Map<String, Class<?>> resource2EntityClassMap = new HashMap<>(32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制sql处理器
|
||||||
|
*/
|
||||||
|
private final static Map<Integer, AbstractDataAuthSqlHandler<?>> sqlHandlerMap = new HashMap<>(8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql处理器id : 需要限制的表对应的类 : 该表用于 与数据权限限制的表做表关联 的列名
|
||||||
|
*/
|
||||||
|
private final static Map<Integer, Map<Class<?>, String>> sqlHandlerId2TargetClass2FieldNameMap = new HashMap<>(8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加需要权限限制的resource
|
||||||
|
*/
|
||||||
|
public static void addNeedAuthResource(String needAuthResource) {
|
||||||
|
needAuthResourceSet.add(needAuthResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是 需要权限限制的resource
|
||||||
|
*/
|
||||||
|
public static boolean isNeedAuthResource(String resource) {
|
||||||
|
return needAuthResourceSet.contains(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加需要权限限制的resource:方法名
|
||||||
|
*/
|
||||||
|
public static void addNeedAuthMethod(String resource, Set<String> methodSet) {
|
||||||
|
needAuthMethodMap.put(resource, methodSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是 需要权限限制的方法
|
||||||
|
*/
|
||||||
|
public static boolean isNeedAuthMethod(String resource, String methodName) {
|
||||||
|
Set<String> needAuthMethod = DataAuthCache.needAuthMethodMap.get(resource);
|
||||||
|
return ObjectUtils.isNotEmpty(needAuthMethod) && needAuthMethod.contains(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加需要权限限制的resource:方法名
|
||||||
|
*/
|
||||||
|
public static void addNotNeedAuthMethod(String resource, Set<String> methodSet) {
|
||||||
|
notNeedAuthMethodMap.put(resource, methodSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是 不需要权限限制的方法
|
||||||
|
*/
|
||||||
|
public static boolean isNotNeedAuthMethod(String resource, String methodName) {
|
||||||
|
Set<String> methodSet = DataAuthCache.notNeedAuthMethodMap.get(resource);
|
||||||
|
return ObjectUtils.isNotEmpty(methodSet) && methodSet.contains(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 resource: 对应实体类类对象 关联关系
|
||||||
|
*/
|
||||||
|
public static void addResource2EntityClassMap(String resource, Class<?> entityClass) {
|
||||||
|
resource2EntityClassMap.put(resource, entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过resource获取其对应实体类
|
||||||
|
*/
|
||||||
|
public static Class<?> getEntityClassByResource(String resource) {
|
||||||
|
return resource2EntityClassMap.get(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加数据权限限制sql处理器
|
||||||
|
*/
|
||||||
|
public static void addSqlHandler(AbstractDataAuthSqlHandler<?> sqlHandler) {
|
||||||
|
sqlHandlerMap.put(sqlHandler.getId(), sqlHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加数据权限限制sql处理器id获取对应处理器
|
||||||
|
*/
|
||||||
|
public static AbstractDataAuthSqlHandler<?> getSqlHandlerById(Integer id) {
|
||||||
|
return sqlHandlerMap.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加数据权限限制sql处理器
|
||||||
|
*/
|
||||||
|
public static void addSqlHandlerId2TargetClass2FieldNameMap(Integer sqlHandlerId,
|
||||||
|
Map<Class<?>, String> targetClass2RelationColumnMap) {
|
||||||
|
sqlHandlerId2TargetClass2FieldNameMap.put(sqlHandlerId, targetClass2RelationColumnMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据sqlHandlerId和targetEntityClass 获取 其用于表关联的列名
|
||||||
|
*
|
||||||
|
* @param sqlHandlerId SqlHandler的id
|
||||||
|
* @param targetEntityClass 需要限制的表对应的类对象
|
||||||
|
* @return 该表用于 与限制表做表关联的字段名
|
||||||
|
*/
|
||||||
|
public static String getFieldName(Integer sqlHandlerId, Class<?> targetEntityClass) {
|
||||||
|
Map<Class<?>, String> classStringMap = sqlHandlerId2TargetClass2FieldNameMap.get(sqlHandlerId);
|
||||||
|
return classStringMap.get(targetEntityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.entity;
|
||||||
|
|
||||||
|
import org.slf4j.helpers.MessageFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制常量类
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/22 14:21
|
||||||
|
*/
|
||||||
|
public interface DataAuthConstants {
|
||||||
|
|
||||||
|
String DEFAULT_ALIAS = "df_als";
|
||||||
|
|
||||||
|
String ERROR_MSG_NO_TABLE_INFO = "entity class [{}] TableInfo cannot be null, make sure MP can load that mapper.";
|
||||||
|
|
||||||
|
String ERROR_MSG_NO_FIELD_INFO = "can not found field [{}] corresponding field name.";
|
||||||
|
|
||||||
|
static String notExistsTableInfo(String entityClassName) {
|
||||||
|
return MessageFormatter.format(ERROR_MSG_NO_TABLE_INFO, entityClassName).getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String notExistsFieldInfo(String fieldName) {
|
||||||
|
return MessageFormatter.format(ERROR_MSG_NO_FIELD_INFO, fieldName).getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.entity;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限sql解析后的结果
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/5 8:48
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public class SqlHandlerResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否拥有全部权限
|
||||||
|
*/
|
||||||
|
private Boolean hadAll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否没有权限
|
||||||
|
*/
|
||||||
|
private Boolean hadNon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否解析过sql
|
||||||
|
* <p>
|
||||||
|
* hadAll = true || hadNon = true时,
|
||||||
|
* 即使不需要修改sql,parsed = true
|
||||||
|
*/
|
||||||
|
private Boolean parsed;
|
||||||
|
|
||||||
|
public static SqlHandlerResult hadAll() {
|
||||||
|
SqlHandlerResult result = new SqlHandlerResult();
|
||||||
|
result.setHadAll(true);
|
||||||
|
result.setHadNon(false);
|
||||||
|
result.setParsed(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlHandlerResult hadNon() {
|
||||||
|
SqlHandlerResult result = new SqlHandlerResult();
|
||||||
|
result.setHadAll(false);
|
||||||
|
result.setHadNon(true);
|
||||||
|
result.setParsed(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlHandlerResult nonData(boolean parsed) {
|
||||||
|
SqlHandlerResult result = new SqlHandlerResult();
|
||||||
|
result.setHadAll(false);
|
||||||
|
result.setHadNon(parsed);
|
||||||
|
result.setParsed(parsed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlHandlerResult handle() {
|
||||||
|
SqlHandlerResult result = new SqlHandlerResult();
|
||||||
|
result.setHadAll(false);
|
||||||
|
result.setHadNon(false);
|
||||||
|
result.setParsed(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqlHandlerResult() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.core.util;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder;
|
||||||
|
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mybatis mapper resource相关工具
|
||||||
|
* <p>
|
||||||
|
* 在mybatis中,每个mapper方法对应一个字符串类型的resource
|
||||||
|
* resource的值有两种生成方式
|
||||||
|
* 1、当该方法没有对应的xml实现时:{@link MapperAnnotationBuilder} at line 109 , {@link MybatisMapperAnnotationBuilder} at line 84
|
||||||
|
* 2、当该方法有对应的xml实现时:{@link MapperAnnotationBuilder} at line 166, {@link MybatisMapperAnnotationBuilder} at line 161
|
||||||
|
* 这两种我们都需要拦截,为了方便后续操作,我们直接统一处理
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/26 11:40
|
||||||
|
*/
|
||||||
|
public class ResourceUtils {
|
||||||
|
|
||||||
|
private static final String MAPPER_SUFFIX = ".java (best guess)";
|
||||||
|
|
||||||
|
private static final String XML_SUFFIX = ".xml";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据mybatis提供的resource 获取 DataAuth过程中需要的resourceKey
|
||||||
|
*
|
||||||
|
* @param resource 根据mybatis提供的resource
|
||||||
|
* @return DataAuth过程中需要的resourceKey
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthInterceptor
|
||||||
|
*/
|
||||||
|
public static String getResourceKey(String resource) {
|
||||||
|
String fileSeparator = System.getProperties().getProperty("file.separator");
|
||||||
|
resource = resource.replace("\\", fileSeparator);
|
||||||
|
if (resource.contains(MAPPER_SUFFIX)) {
|
||||||
|
if (resource.contains(fileSeparator)) {
|
||||||
|
resource = resource.substring(resource.lastIndexOf(fileSeparator) + 1);
|
||||||
|
}
|
||||||
|
return resource.substring(0, resource.indexOf(MAPPER_SUFFIX));
|
||||||
|
} else if (resource.contains(XML_SUFFIX)) {
|
||||||
|
if (resource.contains(fileSeparator)) {
|
||||||
|
resource = resource.substring(resource.lastIndexOf(fileSeparator) + 1);
|
||||||
|
}
|
||||||
|
return resource.substring(0, resource.indexOf(XML_SUFFIX));
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("invalid resource: " + resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据mapper类 获取 DataAuth过程中需要的resourceKey
|
||||||
|
*
|
||||||
|
* @param mapperClass mapper类类对象
|
||||||
|
* @return DataAuth过程中需要的resourceKey
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthSqlInjector
|
||||||
|
*/
|
||||||
|
public static String getResourceKey(Class<?> mapperClass) {
|
||||||
|
return mapperClass.getSimpleName();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.impl;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthCacheHelper;
|
||||||
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
|
||||||
|
import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化添加数据权限限制配置
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2020/12/21 11:45
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DataAuthCacheInit implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysPermissionService permissionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
|
||||||
|
DataAuthCacheHelper helper = new DataAuthCacheHelper()
|
||||||
|
.setSqlHandler(new MyRoleDataAuthSqlHandler(permissionService))
|
||||||
|
.addTargetEntityClass(SysUserDO.class)
|
||||||
|
.setTargetFieldName(SysUserDO::getId)
|
||||||
|
.bind();
|
||||||
|
helper.cache();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.AbstractDataAuthSqlHandler;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthStrategy;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthCache;
|
||||||
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义数据权限sql处理策略类
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 17:54 2021/1/29
|
||||||
|
*/
|
||||||
|
public class MyDataAuthStrategy implements DataAuthStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doParse(Select selectStatement, Class<?> entityClass) {
|
||||||
|
AbstractDataAuthSqlHandler<?> dataAuthSqlHandler = DataAuthCache.getSqlHandlerById(SqlHandlerIdEnum.ROLE.getId());
|
||||||
|
dataAuthSqlHandler.doParse(selectStatement, entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.impl;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.AbstractDataAuthSqlHandler;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.getter.TargetDataGetter;
|
||||||
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
|
||||||
|
import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.ValueListExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||||
|
import net.sf.jsqlparser.schema.Column;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色数据权限限制sql处理器
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/19 15:58
|
||||||
|
*/
|
||||||
|
public class MyRoleDataAuthSqlHandler extends AbstractDataAuthSqlHandler<Set<Long>> {
|
||||||
|
|
||||||
|
public MyRoleDataAuthSqlHandler(SysPermissionService permissionService) {
|
||||||
|
super(SysUserDO.class,
|
||||||
|
"dr_usr",
|
||||||
|
new TargetDataGetter<>(permissionService, SysPermissionService::getCurrentUserDataPermission),
|
||||||
|
SysUserDO::getId,
|
||||||
|
SysUserDO::getDeptId,
|
||||||
|
new InExpression());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return SqlHandlerIdEnum.ROLE.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean parseIfDataAbsent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setOperatorValue() {
|
||||||
|
InExpression operator = (InExpression) this.operator;
|
||||||
|
|
||||||
|
Column whereColumn = new Column(whereColumnName);
|
||||||
|
whereColumn.setTable(getLimitTable());
|
||||||
|
operator.setLeftExpression(whereColumn);
|
||||||
|
|
||||||
|
ValueListExpression listExpression = new ValueListExpression();
|
||||||
|
Set<Long> data = dataGetter.getData();
|
||||||
|
if (data != null) {
|
||||||
|
List<Expression> expressions = new ArrayList<>();
|
||||||
|
data.forEach(s -> {
|
||||||
|
LongValue stringValue = new LongValue(s);
|
||||||
|
expressions.add(stringValue);
|
||||||
|
});
|
||||||
|
listExpression.setExpressionList(new ExpressionList(expressions));
|
||||||
|
operator.setRightExpression(listExpression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean dataIsFull(Set<Long> data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth.impl;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限sql处理器id枚举类
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/19 15:51
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum SqlHandlerIdEnum {
|
||||||
|
|
||||||
|
ROLE(1);
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package cn.iocoder.dashboard.framework.dataauth;
|
||||||
|
/**
|
||||||
|
* 数据权限限制SQL自动注入
|
||||||
|
* <p>
|
||||||
|
* 1、利用mp的mapper扫描将带有需要限制和不需要限制的类/方法信息缓存。
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthSqlInjector
|
||||||
|
* <p>
|
||||||
|
* 2、利用mp的拦截器对执行的方法拦截,并通过1中的信息判断是否需要进行限制。
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthInterceptor
|
||||||
|
* <p>
|
||||||
|
* 3、配置数据权限限制的处理器以及处理策略
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.AbstractDataAuthSqlHandler
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthStrategy
|
||||||
|
* <p>
|
||||||
|
* 4、在系统初始化完成后,加载权限限制的表字短信,并全局缓存。
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthCacheHelper
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.core.entity.DataAuthCache
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* 使用:
|
||||||
|
* 1、配置
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.impl.DataAuthCacheInit
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.impl.MyDataAuthStrategy
|
||||||
|
* @see cn.iocoder.dashboard.framework.dataauth.impl.MyRoleDataAuthSqlHandler
|
||||||
|
* 2、添加@DataAuth注解
|
||||||
|
* @see cn.iocoder.dashboard.modules.system.dal.mysql.user.SysUserMapper#selectPage(cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageReqVO, java.util.Collection)
|
||||||
|
* 3、登录后请求对应接口即可, 日志中有输出前后sql
|
||||||
|
*/
|
|
@ -1,5 +1,9 @@
|
||||||
package cn.iocoder.dashboard.framework.mybatis.config;
|
package cn.iocoder.dashboard.framework.mybatis.config;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthInterceptor;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.component.DataAuthSqlInjector;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.impl.MyDataAuthStrategy;
|
||||||
|
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
@ -19,8 +23,19 @@ public class MybatisConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
||||||
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件
|
// 分页插件
|
||||||
|
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||||
|
// 数据权限限制
|
||||||
|
mybatisPlusInterceptor.addInnerInterceptor(new DataAuthInterceptor(new MyDataAuthStrategy()));
|
||||||
return mybatisPlusInterceptor;
|
return mybatisPlusInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限限制中 用于扫描mapper,加载需要做权限限制的数据的类/方法
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ISqlInjector sqlInjector() {
|
||||||
|
return new DataAuthSqlInjector();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package cn.iocoder.dashboard.modules.system.dal.mysql.user;
|
package cn.iocoder.dashboard.modules.system.dal.mysql.user;
|
||||||
|
|
||||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.annotion.DataAuth;
|
||||||
|
import cn.iocoder.dashboard.framework.dataauth.core.annotion.DataAuthIgnore;
|
||||||
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
|
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
|
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO;
|
||||||
|
@ -27,6 +29,7 @@ public interface SysUserMapper extends BaseMapperX<SysUserDO> {
|
||||||
return selectOne(new QueryWrapper<SysUserDO>().eq("email", email));
|
return selectOne(new QueryWrapper<SysUserDO>().eq("email", email));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DataAuth
|
||||||
default PageResult<SysUserDO> selectPage(SysUserPageReqVO reqVO, Collection<Long> deptIds) {
|
default PageResult<SysUserDO> selectPage(SysUserPageReqVO reqVO, Collection<Long> deptIds) {
|
||||||
return selectPage(reqVO, new QueryWrapperX<SysUserDO>()
|
return selectPage(reqVO, new QueryWrapperX<SysUserDO>()
|
||||||
.likeIfPresent("username", reqVO.getUsername())
|
.likeIfPresent("username", reqVO.getUsername())
|
||||||
|
|
|
@ -106,4 +106,11 @@ public interface SysPermissionService extends SecurityPermissionFrameworkService
|
||||||
*/
|
*/
|
||||||
void processUserDeleted(Long userId);
|
void processUserDeleted(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户的数据权限
|
||||||
|
*
|
||||||
|
* @return 当前用户的数据权限
|
||||||
|
*/
|
||||||
|
Set<Long> getCurrentUserDataPermission();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,15 @@ public interface SysRoleService {
|
||||||
*/
|
*/
|
||||||
List<SysRoleDO> listRolesFromCache(Collection<Long> ids);
|
List<SysRoleDO> listRolesFromCache(Collection<Long> ids);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定角色的数据权限集合
|
||||||
|
*
|
||||||
|
* @param ids 角色编号数组
|
||||||
|
* @return 角色数据权限数据
|
||||||
|
*/
|
||||||
|
Set<Long> listDataScopeDeptIdsByRoleIdsFromCache(Collection<Long> ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断角色数组中,是否有管理员
|
* 判断角色数组中,是否有管理员
|
||||||
*
|
*
|
||||||
|
|
|
@ -249,6 +249,12 @@ public class SysPermissionServiceImpl implements SysPermissionService {
|
||||||
// TODO 实现我
|
// TODO 实现我
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> getCurrentUserDataPermission() {
|
||||||
|
Set<Long> roleIds = SecurityUtils.getLoginUserRoleIds();
|
||||||
|
return roleService.listDataScopeDeptIdsByRoleIdsFromCache(roleIds);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermission(String permission) {
|
public boolean hasPermission(String permission) {
|
||||||
return hasAnyPermissions(permission);
|
return hasAnyPermissions(permission);
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.dashboard.modules.system.service.permission.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
|
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
|
||||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||||
|
@ -11,8 +12,8 @@ import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRole
|
||||||
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRolePageReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRolePageReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleUpdateReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleUpdateReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.convert.permission.SysRoleConvert;
|
import cn.iocoder.dashboard.modules.system.convert.permission.SysRoleConvert;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysRoleMapper;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.permission.SysRoleDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.permission.SysRoleDO;
|
||||||
|
import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysRoleMapper;
|
||||||
import cn.iocoder.dashboard.modules.system.enums.permission.RoleCodeEnum;
|
import cn.iocoder.dashboard.modules.system.enums.permission.RoleCodeEnum;
|
||||||
import cn.iocoder.dashboard.modules.system.enums.permission.SysRoleTypeEnum;
|
import cn.iocoder.dashboard.modules.system.enums.permission.SysRoleTypeEnum;
|
||||||
import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysRoleProducer;
|
import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysRoleProducer;
|
||||||
|
@ -53,7 +54,7 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||||
/**
|
/**
|
||||||
* 角色缓存
|
* 角色缓存
|
||||||
* key:角色编号 {@link SysRoleDO#getId()}
|
* key:角色编号 {@link SysRoleDO#getId()}
|
||||||
*
|
* <p>
|
||||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||||
*/
|
*/
|
||||||
private volatile Map<Long, SysRoleDO> roleCache;
|
private volatile Map<Long, SysRoleDO> roleCache;
|
||||||
|
@ -137,6 +138,30 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> listDataScopeDeptIdsByRoleIdsFromCache(Collection<Long> ids) {
|
||||||
|
Set<Long> dataScopeDeptIdSet = new HashSet<>();
|
||||||
|
if (ObjectUtil.isEmpty(ids)) {
|
||||||
|
return dataScopeDeptIdSet;
|
||||||
|
}
|
||||||
|
// TODO FROM 芋艿 to zzf:是不是遍历 ids 去 roleCache 获取会好点?因为一个人拥有的角色少。
|
||||||
|
// roleCache.values() = List<SysRoleDO>, 遍历ids没法直接与之比较,还是得遍历roleCache.values()才能比较。
|
||||||
|
roleCache.values()
|
||||||
|
.forEach(roleDO -> {
|
||||||
|
if (ids.contains(roleDO.getId())) {
|
||||||
|
dataScopeDeptIdSet.addAll(roleDO.getDataScopeDeptIds());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//TODO 模拟数据,后续删除
|
||||||
|
if (dataScopeDeptIdSet.size() == 0) {
|
||||||
|
dataScopeDeptIdSet.add(1L);
|
||||||
|
dataScopeDeptIdSet.add(2L);
|
||||||
|
dataScopeDeptIdSet.add(3L);
|
||||||
|
dataScopeDeptIdSet.add(4L);
|
||||||
|
}
|
||||||
|
return dataScopeDeptIdSet;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAnyAdmin(Collection<SysRoleDO> roleList) {
|
public boolean hasAnyAdmin(Collection<SysRoleDO> roleList) {
|
||||||
if (CollectionUtil.isEmpty(roleList)) {
|
if (CollectionUtil.isEmpty(roleList)) {
|
||||||
|
@ -237,13 +262,13 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验角色的唯一字段是否重复
|
* 校验角色的唯一字段是否重复
|
||||||
*
|
* <p>
|
||||||
* 1. 是否存在相同名字的角色
|
* 1. 是否存在相同名字的角色
|
||||||
* 2. 是否存在相同编码的角色
|
* 2. 是否存在相同编码的角色
|
||||||
*
|
*
|
||||||
* @param name 角色名字
|
* @param name 角色名字
|
||||||
* @param code 角色额编码
|
* @param code 角色额编码
|
||||||
* @param id 角色编号
|
* @param id 角色编号
|
||||||
*/
|
*/
|
||||||
private void checkDuplicateRole(String name, String code, Long id) {
|
private void checkDuplicateRole(String name, String code, Long id) {
|
||||||
// 1. 该 name 名字被其它角色所使用
|
// 1. 该 name 名字被其它角色所使用
|
||||||
|
|
Loading…
Reference in New Issue