This commit is contained in:
XSWL1018 2024-09-10 17:39:57 +08:00
parent e60bdb6407
commit d06fa02df2
21 changed files with 227 additions and 107 deletions

View File

@ -1,48 +0,0 @@
package com.ruoyi.common.context.dataSecurity;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.enums.SqlType;
import com.ruoyi.common.model.JoinTableModel;
import com.ruoyi.common.model.WhereModel;
public class DataSecurityContextHolder {
private static final ThreadLocal<JSONObject> DATA_SECURITY_SQL_CONTEXT_HOLDER = new ThreadLocal<>();
public static void startDataSecurity() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("isSecurity", Boolean.TRUE);
jsonObject.put(SqlType.WHERE.getSqlType(), new JSONArray());
jsonObject.put(SqlType.JOIN.getSqlType(), new JSONArray());
DATA_SECURITY_SQL_CONTEXT_HOLDER.set(jsonObject);
}
public static void addWhereParam(WhereModel whereModel) {
DATA_SECURITY_SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.WHERE.getSqlType()).add(whereModel);
}
public static void clearCache() {
DATA_SECURITY_SQL_CONTEXT_HOLDER.remove();
}
public static boolean isSecurity() {
return DATA_SECURITY_SQL_CONTEXT_HOLDER.get() != null
&& DATA_SECURITY_SQL_CONTEXT_HOLDER.get().getBooleanValue("isSecurity");
}
public static JSONArray getWhere() {
return DATA_SECURITY_SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.WHERE.getSqlType());
}
public static void addJoinTable(JoinTableModel joinTableModel) {
DATA_SECURITY_SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType()).add(joinTableModel);
}
public static JSONArray getJoinTables() {
return DATA_SECURITY_SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType());
}
}

View File

@ -1,14 +0,0 @@
package com.ruoyi.common.utils;
import com.ruoyi.common.context.dataSecurity.DataSecurityContextHolder;
public class DataSecurityUtil {
public static void closeDataSecurity() {
DataSecurityContextHolder.clearCache();
}
public static void startDataSecurity() {
DataSecurityContextHolder.startDataSecurity();
}
}

View File

@ -44,6 +44,12 @@
<artifactId>ruoyi-middleware-starter</artifactId> <artifactId>ruoyi-middleware-starter</artifactId>
<version>${ruoyi.version}</version> <version>${ruoyi.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-midleware-mybatis-interceptor</artifactId>
<version>${ruoyi.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -52,6 +58,7 @@
<module>ruoyi-middleware-minio</module> <module>ruoyi-middleware-minio</module>
<module>ruoyi-middleware-redis</module> <module>ruoyi-middleware-redis</module>
<module>ruoyi-middleware-starter</module> <module>ruoyi-middleware-starter</module>
<module>ruoyi-midleware-mybatis-interceptor</module>
</modules> </modules>
<packaging>pom</packaging> <packaging>pom</packaging>
</project> </project>

View File

@ -32,6 +32,11 @@
<artifactId>ruoyi-middleware-redis</artifactId> <artifactId>ruoyi-middleware-redis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-midleware-mybatis-interceptor</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-middleware</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.8.3.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-midleware-mybatis-interceptor</artifactId>
<properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,16 @@
package annotation;
import com.ruoyi.common.enums.DataSecurityStrategy;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSecurity {
public DataSecurityStrategy strategy() default DataSecurityStrategy.CREEATE_BY;
public String table() default "";
public String joinTableAlise() default "";
}

View File

@ -0,0 +1,10 @@
package annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MybatisHandlerOrder {
public int value() default 0;
}

View File

@ -1,31 +1,26 @@
package com.ruoyi.framework.aspectj; package aspectj;
import java.util.List;
import context.dataSecurity.SqlContextHolder;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.sql.DataSecurity; import com.ruoyi.common.annotation.sql.DataSecurity;
import com.ruoyi.common.context.dataSecurity.DataSecurityContextHolder;
import com.ruoyi.common.enums.DataSecurityStrategy;
import com.ruoyi.common.model.JoinTableModel; import com.ruoyi.common.model.JoinTableModel;
import com.ruoyi.common.model.WhereModel; import com.ruoyi.common.model.WhereModel;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import ch.qos.logback.core.util.StringUtil;
@Aspect @Aspect
@Component @Component
public class DataSecurityAspect { public class DataSecurityAspect {
@Before(value = "@annotation(dataSecurity)") @Before(value = "@annotation(dataSecurity)")
public void doBefore(final JoinPoint point, DataSecurity dataSecurity) throws Throwable { public void doBefore(final JoinPoint point, DataSecurity dataSecurity) throws Throwable {
DataSecurityContextHolder.startDataSecurity(); SqlContextHolder.startDataSecurity();
switch (dataSecurity.strategy()) { switch (dataSecurity.strategy()) {
case CREEATE_BY: case CREEATE_BY:
WhereModel createByModel = new WhereModel(); WhereModel createByModel = new WhereModel();
@ -34,7 +29,7 @@ public class DataSecurityAspect {
createByModel.setWhereColumn("create_by"); createByModel.setWhereColumn("create_by");
createByModel.setMethod(WhereModel.METHOD_EQUAS); createByModel.setMethod(WhereModel.METHOD_EQUAS);
createByModel.setConnectType(WhereModel.CONNECT_AND); createByModel.setConnectType(WhereModel.CONNECT_AND);
DataSecurityContextHolder.addWhereParam(createByModel); SqlContextHolder.addWhereParam(createByModel);
break; break;
case USER_ID: case USER_ID:
WhereModel userIdModel = new WhereModel(); WhereModel userIdModel = new WhereModel();
@ -43,7 +38,7 @@ public class DataSecurityAspect {
userIdModel.setValue(SecurityUtils.getUserId()); userIdModel.setValue(SecurityUtils.getUserId());
userIdModel.setConnectType(WhereModel.CONNECT_AND); userIdModel.setConnectType(WhereModel.CONNECT_AND);
userIdModel.setMethod(WhereModel.METHOD_EQUAS); userIdModel.setMethod(WhereModel.METHOD_EQUAS);
DataSecurityContextHolder.addWhereParam(userIdModel); SqlContextHolder.addWhereParam(userIdModel);
break; break;
case JOINTABLE_CREATE_BY: case JOINTABLE_CREATE_BY:
JoinTableModel createByTableModel = new JoinTableModel(); JoinTableModel createByTableModel = new JoinTableModel();
@ -56,7 +51,7 @@ public class DataSecurityAspect {
createByTableModel.setFromTableColumn("create_by"); createByTableModel.setFromTableColumn("create_by");
createByTableModel.setJoinTableColumn("user_name"); createByTableModel.setJoinTableColumn("user_name");
DataSecurityContextHolder.addJoinTable(createByTableModel); SqlContextHolder.addJoinTable(createByTableModel);
break; break;
case JOINTABLE_USER_ID: case JOINTABLE_USER_ID:
JoinTableModel userIdTableModel = new JoinTableModel(); JoinTableModel userIdTableModel = new JoinTableModel();
@ -69,7 +64,7 @@ public class DataSecurityAspect {
userIdTableModel.setFromTableColumn("user_id"); userIdTableModel.setFromTableColumn("user_id");
userIdTableModel.setJoinTableColumn("user_id"); userIdTableModel.setJoinTableColumn("user_id");
DataSecurityContextHolder.addJoinTable(userIdTableModel); SqlContextHolder.addJoinTable(userIdTableModel);
break; break;
default: default:
@ -80,6 +75,6 @@ public class DataSecurityAspect {
@After(value = " @annotation(dataSecurity)") @After(value = " @annotation(dataSecurity)")
public void doAfter(final JoinPoint point, DataSecurity dataSecurity) { public void doAfter(final JoinPoint point, DataSecurity dataSecurity) {
DataSecurityContextHolder.clearCache(); SqlContextHolder.clearCache();
} }
} }

View File

@ -0,0 +1,45 @@
package context.dataSecurity;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.enums.SqlType;
import com.ruoyi.common.model.JoinTableModel;
import com.ruoyi.common.model.WhereModel;
public class SqlContextHolder {
private static final ThreadLocal<JSONObject> SQL_CONTEXT_HOLDER = new ThreadLocal<>();
public static void startDataSecurity() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("isSecurity", Boolean.TRUE);
jsonObject.put(SqlType.WHERE.getSqlType(), new JSONArray());
jsonObject.put(SqlType.JOIN.getSqlType(), new JSONArray());
SQL_CONTEXT_HOLDER.set(jsonObject);
}
public static void addWhereParam(WhereModel whereModel) {
SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.WHERE.getSqlType()).add(whereModel);
}
public static void clearCache() {
SQL_CONTEXT_HOLDER.remove();
}
public static boolean isSecurity() {
return SQL_CONTEXT_HOLDER.get() != null
&& SQL_CONTEXT_HOLDER.get().getBooleanValue("isSecurity");
}
public static JSONArray getWhere() {
return SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.WHERE.getSqlType());
}
public static void addJoinTable(JoinTableModel joinTableModel) {
SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType()).add(joinTableModel);
}
public static JSONArray getJoinTables() {
return SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType());
}
}

View File

@ -1,7 +1,7 @@
package com.ruoyi.common.context.page; package context.page;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.context.page.model.PageInfo; import context.page.model.PageInfo;
public class PageContextHolder { public class PageContextHolder {
private static final ThreadLocal<JSONObject> PAGE_CONTEXT_HOLDER = new ThreadLocal<>(); private static final ThreadLocal<JSONObject> PAGE_CONTEXT_HOLDER = new ThreadLocal<>();

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.context.page.model; package context.page.model;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.context.page.model; package context.page.model;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.context.page.model; package context.page.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,6 +1,5 @@
package com.ruoyi.framework.interceptor.mybatis; package interceptor.mybatis;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -18,10 +17,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.sql.MybatisHandlerOrder; import com.ruoyi.common.annotation.sql.MybatisHandlerOrder;
import com.ruoyi.common.handler.sql.MybatisAfterHandler;
import com.ruoyi.common.handler.sql.MybatisPreHandler;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import sql.MybatisAfterHandler;
import sql.MybatisPreHandler;
@Component @Component
@Intercepts({ @Intercepts({

View File

@ -1,7 +1,7 @@
package com.ruoyi.common.handler.sql; package sql;
public interface MybatisAfterHandler { public interface MybatisAfterHandler {
Object handleObject(Object object) throws Throwable; Object handleObject(Object object) throws Throwable;
} }

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.handler.sql; package sql;
import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.Executor;
@ -12,4 +12,4 @@ public interface MybatisPreHandler {
void preHandle(Executor executor, MappedStatement mappedStatement, Object params, void preHandle(Executor executor, MappedStatement mappedStatement, Object params,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql)
throws Throwable; throws Throwable;
} }

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.handler.sql.dataSecurity; package sql.dataSecurity;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; import java.util.List;
@ -13,8 +13,8 @@ import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import com.ruoyi.common.annotation.sql.MybatisHandlerOrder; import com.ruoyi.common.annotation.sql.MybatisHandlerOrder;
import com.ruoyi.common.context.dataSecurity.DataSecurityContextHolder; import context.dataSecurity.SqlContextHolder;
import com.ruoyi.common.handler.sql.MybatisPreHandler; import sql.MybatisPreHandler;
import com.ruoyi.common.model.JoinTableModel; import com.ruoyi.common.model.JoinTableModel;
import com.ruoyi.common.model.WhereModel; import com.ruoyi.common.model.WhereModel;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@ -44,7 +44,7 @@ public class DataSecurityPreHandler implements MybatisPreHandler {
@Override @Override
public void preHandle(Executor executor, MappedStatement mappedStatement, Object params, RowBounds rowBounds, public void preHandle(Executor executor, MappedStatement mappedStatement, Object params, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable { ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable {
if (DataSecurityContextHolder.isSecurity()) { if (SqlContextHolder.isSecurity()) {
Statement sql = parseSql(SqlUtil.parseSql(boundSql.getSql())); Statement sql = parseSql(SqlUtil.parseSql(boundSql.getSql()));
sqlFiled.set(boundSql, sql.toString()); sqlFiled.set(boundSql, sql.toString());
} }
@ -67,10 +67,10 @@ public class DataSecurityPreHandler implements MybatisPreHandler {
Expression expWhere = plain.getWhere(); Expression expWhere = plain.getWhere();
StringBuilder whereParam = new StringBuilder(" "); StringBuilder whereParam = new StringBuilder(" ");
String where = expWhere != null ? expWhere.toString() : null; String where = expWhere != null ? expWhere.toString() : null;
if (DataSecurityContextHolder.getWhere() == null || DataSecurityContextHolder.getWhere().size() <= 0) { if (SqlContextHolder.getWhere() == null || SqlContextHolder.getWhere().size() <= 0) {
return; return;
} }
DataSecurityContextHolder.getWhere().forEach(item -> { SqlContextHolder.getWhere().forEach(item -> {
whereParam.append(((WhereModel) item).getSqlString()); whereParam.append(((WhereModel) item).getSqlString());
}); });
where = StringUtils.isEmpty(where) ? whereParam.toString().substring(5, whereParam.length()) where = StringUtils.isEmpty(where) ? whereParam.toString().substring(5, whereParam.length())
@ -80,10 +80,10 @@ public class DataSecurityPreHandler implements MybatisPreHandler {
private static void handleJoin(Select select) { private static void handleJoin(Select select) {
PlainSelect selectBody = select.getPlainSelect(); PlainSelect selectBody = select.getPlainSelect();
if (DataSecurityContextHolder.getJoinTables() == null || DataSecurityContextHolder.getJoinTables().size() <= 0) { if (SqlContextHolder.getJoinTables() == null || SqlContextHolder.getJoinTables().size() <= 0) {
return; return;
} }
DataSecurityContextHolder.getJoinTables().forEach(item -> { SqlContextHolder.getJoinTables().forEach(item -> {
JoinTableModel tableModel = (JoinTableModel) item; JoinTableModel tableModel = (JoinTableModel) item;
Table table = new Table(tableModel.getJoinTable()); Table table = new Table(tableModel.getJoinTable());
table.setAlias(new Alias(tableModel.getJoinTableAlise())); table.setAlias(new Alias(tableModel.getJoinTableAlise()));

View File

@ -1,14 +1,13 @@
package com.ruoyi.common.handler.sql.page; package sql.page;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.sql.MybatisHandlerOrder; import com.ruoyi.common.annotation.sql.MybatisHandlerOrder;
import com.ruoyi.common.context.page.PageContextHolder; import context.page.PageContextHolder;
import com.ruoyi.common.context.page.model.TableInfo; import context.page.model.TableInfo;
import com.ruoyi.common.handler.sql.MybatisAfterHandler; import sql.MybatisAfterHandler;
@MybatisHandlerOrder(1) @MybatisHandlerOrder(1)
@Component @Component

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.handler.sql.page; package sql.page;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.sql.SQLException; import java.sql.SQLException;
@ -17,9 +17,9 @@ import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import com.ruoyi.common.annotation.sql.MybatisHandlerOrder; import com.ruoyi.common.annotation.sql.MybatisHandlerOrder;
import com.ruoyi.common.context.page.PageContextHolder; import context.page.PageContextHolder;
import com.ruoyi.common.context.page.model.PageInfo; import context.page.model.PageInfo;
import com.ruoyi.common.handler.sql.MybatisPreHandler; import sql.MybatisPreHandler;
import com.ruoyi.common.utils.sql.SqlUtil; import com.ruoyi.common.utils.sql.SqlUtil;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statement;

View File

@ -0,0 +1,15 @@
package util;
import context.dataSecurity.SqlContextHolder;
public class DataSecurityUtil {
public static void closeDataSecurity() {
SqlContextHolder.clearCache();
}
public static void startDataSecurity() {
SqlContextHolder.startDataSecurity();
}
}

View File

@ -0,0 +1,64 @@
package util;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.StringUtils;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.statement.Statement;
import java.io.StringReader;
/**
* sql操作工具类
*
* @author ruoyi
*/
public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()";
/**
* 仅支持字母数字下划线空格逗号小数点支持多个字段排序
*/
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
private static final CCJSqlParserManager parserManager = new CCJSqlParserManager();
/**
* 检查字符防止注入绕过
*/
public static String escapeOrderBySql(String value) {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new UtilException("参数不符合规范,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value) {
return value.matches(SQL_PATTERN);
}
/**
* SQL关键字检查
*/
public static void filterKeyword(String value) {
if (StringUtils.isEmpty(value)) {
return;
}
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) {
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
throw new UtilException("参数存在SQL注入风险");
}
}
}
public static Statement parseSql(String sql) throws JSQLParserException {
return parserManager.parse(new StringReader(sql));
}
}