mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-14 06:09:03 +08:00
refactor(pigx-common-data): 重构数据权限拦截器实现,使用 JSqlParser 直接操作 AST 修改 WHERE 条件,避免子查询包装导致索引失效
- 支持复杂 SQL 解析失败时自动回退到子查询包装方案 - 优化表引用处理逻辑,优先使用别名限定列名 - 增加 SQL 注入防护,对字符串值进行转义处理 - 重构条件构建逻辑,支持 AND/OR 逻辑组合 - 完善日志记录和异常处理机制
This commit is contained in:
1 parent
173bc6853d
commit
f56a63beba
1 file changed
+139
-137
+139
-137
@@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
@@ -19,12 +21,18 @@ import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author legal
|
||||
* 数据权限内部拦截器
|
||||
* <p>
|
||||
* 参照 MyBatis Plus {@code DataPermissionInterceptor} 的实现思路,
|
||||
* 通过 JSqlParser 解析 SQL 为 AST,使用 {@code PlainSelect.setWhere()} 直接在
|
||||
* WHERE 子句上追加数据权限条件,避免子查询包装导致索引失效。
|
||||
* 对于 UNION 等复杂 SQL 或解析失败的情况,回退到子查询包装方式保证兼容性。
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020/11/29
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -62,155 +70,149 @@ public class DataScopeInnerInterceptor implements DataScopeInterceptor {
|
||||
mpBs.sql(String.format("SELECT %s FROM (%s) temp_data_scope", dataScope.getFunc().getType(), originalSql));
|
||||
return;
|
||||
}
|
||||
// 分割sql语句
|
||||
List<String> where = splitSqlByWhere(originalSql);
|
||||
// 获取表名
|
||||
String tableName = getTableName(originalSql);
|
||||
|
||||
List<Long> deptIds = dataScope.getDeptList();
|
||||
|
||||
// 1.无数据权限限制,则直接返回 0 条数据
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(dataScope.getUsername())) {
|
||||
if (CollUtil.isNotEmpty(where) && where.size() > 1) {
|
||||
originalSql = String.format("%s WHERE 1 = 2 AND %s", where.get(0), where.get(1));
|
||||
} else {
|
||||
originalSql = String.format("%s WHERE 1 = 2", where.get(0));
|
||||
}
|
||||
/*originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2",
|
||||
dataScope.getFunc().getType(), originalSql);*/
|
||||
}
|
||||
// 2.如果为本人权限 + 部门权限控制
|
||||
else if (StrUtil.isNotBlank(dataScope.getUsername()) && CollUtil.isNotEmpty(deptIds)) {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
// 使用配置的组合模式(AND/OR),默认OR保持向后兼容
|
||||
String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword()
|
||||
: DataScopeLogicEnum.OR.getKeyword();
|
||||
originalSql = String.format("%s WHERE %s.%s = '%s' %s %s.%s IN (%s)",
|
||||
where.get(0),
|
||||
tableName,
|
||||
dataScope.getScopeUserName(),
|
||||
dataScope.getUsername(),
|
||||
logicKeyword,
|
||||
tableName,
|
||||
dataScope.getScopeDeptName(),
|
||||
join
|
||||
);
|
||||
if (CollUtil.isNotEmpty(where) && where.size() > 1) {
|
||||
originalSql = originalSql + " AND " + where.get(1);
|
||||
}
|
||||
/*originalSql = String.format(
|
||||
"SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s' %s temp_data_scope.%s IN (%s)",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername(),
|
||||
logicKeyword, dataScope.getScopeDeptName(), join);*/
|
||||
}
|
||||
// 3. 如果为本人
|
||||
else if (StrUtil.isNotBlank(dataScope.getUsername())) {
|
||||
if (CollUtil.isNotEmpty(where) && where.size() > 1) {
|
||||
originalSql = String.format("%s WHERE %s.%s = '%s' AND %s",
|
||||
where.get(0),
|
||||
tableName,
|
||||
dataScope.getScopeUserName(),
|
||||
dataScope.getUsername(),
|
||||
where.get(1)
|
||||
);
|
||||
} else {
|
||||
originalSql = String.format("%s WHERE %s.%s = '%s'",
|
||||
where.get(0),
|
||||
tableName,
|
||||
dataScope.getScopeUserName(),
|
||||
dataScope.getUsername()
|
||||
);
|
||||
}
|
||||
/*originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername());*/
|
||||
}
|
||||
// 4.部门权限控制
|
||||
else {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
if (CollUtil.isNotEmpty(where) && where.size() > 1) {
|
||||
originalSql = String.format("%s WHERE %s.%s IN (%s) AND %s",
|
||||
where.get(0),
|
||||
tableName,
|
||||
dataScope.getScopeDeptName(),
|
||||
join,
|
||||
where.get(1)
|
||||
);
|
||||
} else {
|
||||
originalSql = String.format("%s WHERE %s.%s IN (%s)",
|
||||
where.get(0),
|
||||
tableName,
|
||||
dataScope.getScopeDeptName(),
|
||||
join
|
||||
);
|
||||
}
|
||||
/*originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeDeptName(), join);*/
|
||||
}
|
||||
|
||||
mpBs.sql(originalSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名(优先取别名,其次取原表名)
|
||||
*/
|
||||
private static String getTableName(String originalSql) {
|
||||
if (StrUtil.isBlank(originalSql)) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
Statement statement = CCJSqlParserUtil.parse(originalSql);
|
||||
if (!(statement instanceof Select selectStatement)) {
|
||||
return "";
|
||||
return;
|
||||
}
|
||||
|
||||
// 增加 PlainSelect 类型判断,避免 UNION 等复杂语句强转异常
|
||||
// UNION 等复杂语句无法直接操作 WHERE,回退到子查询包装
|
||||
if (!(selectStatement.getSelectBody() instanceof PlainSelect plainSelect)) {
|
||||
return "";
|
||||
mpBs.sql(buildSubQuerySql(originalSql, dataScope));
|
||||
return;
|
||||
}
|
||||
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
if (fromItem == null) {
|
||||
return "";
|
||||
// 构建数据权限条件表达式
|
||||
Expression scopeExpression = buildScopeExpression(plainSelect, dataScope);
|
||||
if (scopeExpression == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先使用别名
|
||||
if (fromItem.getAlias() != null) {
|
||||
return fromItem.getAlias().getName();
|
||||
}
|
||||
|
||||
// 其次使用原表名
|
||||
if (fromItem instanceof Table table) {
|
||||
return table.getName();
|
||||
// 与原有 WHERE 条件合并(数据权限条件 AND 原条件)
|
||||
Expression existingWhere = plainSelect.getWhere();
|
||||
if (existingWhere != null) {
|
||||
plainSelect.setWhere(new AndExpression(scopeExpression, existingWhere));
|
||||
} else {
|
||||
plainSelect.setWhere(scopeExpression);
|
||||
}
|
||||
|
||||
mpBs.sql(selectStatement.toString());
|
||||
} catch (JSQLParserException e) {
|
||||
log.error("JSQLParserException: {} -- sql: {}", e.getMessage(), originalSql);
|
||||
log.warn("数据权限 SQL 解析失败,回退到子查询包装: {}", originalSql, e);
|
||||
mpBs.sql(buildSubQuerySql(originalSql, dataScope));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割SQL by WHERE
|
||||
* @return 分割结果,失败返回空集合
|
||||
* 根据 DataScope 配置构建数据权限条件表达式
|
||||
* <p>
|
||||
* 使用 {@code CCJSqlParserUtil.parseCondExpression()} 解析条件字符串为 Expression 对象,
|
||||
* 与 MyBatis Plus DataPermissionInterceptor 保持一致的实现方式。
|
||||
*
|
||||
* @param plainSelect 解析后的 PlainSelect 对象
|
||||
* @param dataScope 数据权限参数
|
||||
* @return 数据权限条件表达式,无需处理时返回 null
|
||||
*/
|
||||
private static List<String> splitSqlByWhere(String sql) {
|
||||
if (StrUtil.isBlank(sql)) {
|
||||
return Collections.emptyList();
|
||||
private Expression buildScopeExpression(PlainSelect plainSelect, DataScope dataScope)
|
||||
throws JSQLParserException {
|
||||
List<Long> deptIds = dataScope.getDeptList();
|
||||
String username = dataScope.getUsername();
|
||||
|
||||
// 获取表引用(优先别名,其次表名),用于限定列名
|
||||
String tableRef = getTableReference(plainSelect.getFromItem());
|
||||
String prefix = StrUtil.isNotBlank(tableRef) ? tableRef + "." : "";
|
||||
|
||||
// 1. 无数据权限限制,返回 0 条数据
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(username)) {
|
||||
return CCJSqlParserUtil.parseCondExpression("1 = 2");
|
||||
}
|
||||
try {
|
||||
// 统一转大写匹配,避免遗漏大小写混合情况(如 Where / wHere)
|
||||
String upperSql = sql.toUpperCase();
|
||||
int index = upperSql.indexOf(" WHERE ");
|
||||
if (index != -1) {
|
||||
return List.of(
|
||||
sql.substring(0, index),
|
||||
sql.substring(index + " WHERE ".length())
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("分割SQL异常:{}--:{}", e, sql);
|
||||
|
||||
// 2. 本人权限 + 部门权限:加括号保证与原有条件组合时的优先级
|
||||
if (StrUtil.isNotBlank(username) && CollUtil.isNotEmpty(deptIds)) {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword()
|
||||
: DataScopeLogicEnum.OR.getKeyword();
|
||||
String condSql = String.format("(%s%s = '%s' %s %s%s IN (%s))", prefix, dataScope.getScopeUserName(),
|
||||
escapeSql(username), logicKeyword, prefix, dataScope.getScopeDeptName(), join);
|
||||
return CCJSqlParserUtil.parseCondExpression(condSql);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
|
||||
// 3. 仅本人权限
|
||||
if (StrUtil.isNotBlank(username)) {
|
||||
String condSql = String.format("%s%s = '%s'", prefix, dataScope.getScopeUserName(), escapeSql(username));
|
||||
return CCJSqlParserUtil.parseCondExpression(condSql);
|
||||
}
|
||||
|
||||
// 4. 仅部门权限
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
String condSql = String.format("%s%s IN (%s)", prefix, dataScope.getScopeDeptName(), join);
|
||||
return CCJSqlParserUtil.parseCondExpression(condSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表引用名称(优先别名,其次表名)
|
||||
*
|
||||
* @param fromItem FROM 子句项
|
||||
* @return 表引用名称,无法获取时返回 null
|
||||
*/
|
||||
private String getTableReference(FromItem fromItem) {
|
||||
if (fromItem == null) {
|
||||
return null;
|
||||
}
|
||||
if (fromItem.getAlias() != null) {
|
||||
return fromItem.getAlias().getName();
|
||||
}
|
||||
if (fromItem instanceof Table table) {
|
||||
return table.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退方案:子查询包装(用于 UNION 等复杂 SQL 或解析失败时)
|
||||
*/
|
||||
private String buildSubQuerySql(String originalSql, DataScope dataScope) {
|
||||
List<Long> deptIds = dataScope.getDeptList();
|
||||
String username = dataScope.getUsername();
|
||||
String selectType = dataScope.getFunc().getType();
|
||||
|
||||
// 无权限
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(username)) {
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2", selectType, originalSql);
|
||||
}
|
||||
|
||||
// 本人 + 部门
|
||||
if (StrUtil.isNotBlank(username) && CollUtil.isNotEmpty(deptIds)) {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword()
|
||||
: DataScopeLogicEnum.OR.getKeyword();
|
||||
return String.format(
|
||||
"SELECT %s FROM (%s) temp_data_scope WHERE (temp_data_scope.%s = '%s' %s temp_data_scope.%s IN (%s))",
|
||||
selectType, originalSql, dataScope.getScopeUserName(), escapeSql(username), logicKeyword,
|
||||
dataScope.getScopeDeptName(), join);
|
||||
}
|
||||
|
||||
// 仅本人
|
||||
if (StrUtil.isNotBlank(username)) {
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'", selectType,
|
||||
originalSql, dataScope.getScopeUserName(), escapeSql(username));
|
||||
}
|
||||
|
||||
// 仅部门
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)", selectType,
|
||||
originalSql, dataScope.getScopeDeptName(), join);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义 SQL 字符串值中的单引号,防止 SQL 注入
|
||||
*/
|
||||
private String escapeSql(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace("'", "''");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,11 +227,11 @@ public class DataScopeInnerInterceptor implements DataScopeInterceptor {
|
||||
} else if (parameterObj instanceof Map) {
|
||||
for (Object val : ((Map<?, ?>) parameterObj).values()) {
|
||||
if (val instanceof DataScope) {
|
||||
return (DataScope) val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return (DataScope) val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user