|
@@ -23,6 +23,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
|
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
|
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
|
import java.sql.*;
|
|
import java.sql.*;
|
|
import java.util.*;
|
|
import java.util.*;
|
|
@@ -129,7 +130,10 @@ public class JdbcExecutor {
|
|
public Result<List<Map<String, Object>>> queryForSql(Map<String, String> dbConfig,
|
|
public Result<List<Map<String, Object>>> queryForSql(Map<String, String> dbConfig,
|
|
String sql,
|
|
String sql,
|
|
List<Object> params ) {
|
|
List<Object> params ) {
|
|
- return queryWithDatasource(dbConfig, sql, dataSource -> {
|
|
|
|
|
|
+ if (!StringUtils.hasText(sql)){
|
|
|
|
+ return Result.fail("sql 不存在或为空");
|
|
|
|
+ }
|
|
|
|
+ return customQueryWithDatasource(dbConfig, sql, dataSource -> {
|
|
try(Connection connection = dataSource.getConnection()) {
|
|
try(Connection connection = dataSource.getConnection()) {
|
|
try(PreparedStatement ps = connection.prepareStatement(sql)){
|
|
try(PreparedStatement ps = connection.prepareStatement(sql)){
|
|
// 设置参数
|
|
// 设置参数
|
|
@@ -138,8 +142,7 @@ public class JdbcExecutor {
|
|
ps.setObject(i + 1, params.get(i)); // 参数从 1 开始
|
|
ps.setObject(i + 1, params.get(i)); // 参数从 1 开始
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- logger.info("ps.toString():{}", ps.toString());
|
|
|
|
- logger.info("Executing paginated SQL: {}", sql);
|
|
|
|
|
|
+ logger.info("执行 SQL: {}", sql);
|
|
List<Map<String, Object>> resultList = new ArrayList<>();
|
|
List<Map<String, Object>> resultList = new ArrayList<>();
|
|
try (ResultSet rs = ps.executeQuery()) {
|
|
try (ResultSet rs = ps.executeQuery()) {
|
|
ResultSetMetaData metaData = rs.getMetaData();
|
|
ResultSetMetaData metaData = rs.getMetaData();
|
|
@@ -333,6 +336,7 @@ public class JdbcExecutor {
|
|
Function<ResultSet, T> rowMapper) {
|
|
Function<ResultSet, T> rowMapper) {
|
|
return CompletableFuture.supplyAsync(() -> query(dbConfig, sql, params, rowMapper), executor);
|
|
return CompletableFuture.supplyAsync(() -> query(dbConfig, sql, params, rowMapper), executor);
|
|
}
|
|
}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* 执行数据库查询操作
|
|
* 执行数据库查询操作
|
|
*
|
|
*
|
|
@@ -376,6 +380,23 @@ public class JdbcExecutor {
|
|
}
|
|
}
|
|
return operation.apply(dataSourceResult.getData());
|
|
return operation.apply(dataSourceResult.getData());
|
|
}
|
|
}
|
|
|
|
+ private <T> Result<T> customQueryWithDatasource(Map<String, String> dbConfig,String sql, Function<HikariDataSource, Result<T>> operation) {
|
|
|
|
+ // 检查SQL语句中是否包含潜在的SQL注入风险
|
|
|
|
+ if (customerHandelSqlInjection(sql,"query")) {
|
|
|
|
+ logger.warn("检测到潜在 SQL 注入风险: {}", sql);
|
|
|
|
+ return Result.fail("SQL 含非法字符");
|
|
|
|
+ }
|
|
|
|
+ if (!rateLimiter.allowRequest()) {
|
|
|
|
+ logger.warn("请求被限流");
|
|
|
|
+ return Result.fail("请求被限流,请稍后重试");
|
|
|
|
+ }
|
|
|
|
+ //查询直接使用JdbcTemplate即可,性能与原生没有区别,代码要简化很多
|
|
|
|
+ Result<HikariDataSource> dataSourceResult = poolManager.getDataSource(dbConfig);
|
|
|
|
+ if(!dataSourceResult.isSuccess()){
|
|
|
|
+ return Result.fail(dataSourceResult.getError());
|
|
|
|
+ }
|
|
|
|
+ return operation.apply(dataSourceResult.getData());
|
|
|
|
+ }
|
|
/**
|
|
/**
|
|
* 根据不同数据库应用分页逻辑
|
|
* 根据不同数据库应用分页逻辑
|
|
*/
|
|
*/
|
|
@@ -415,6 +436,7 @@ public class JdbcExecutor {
|
|
try (Connection connection = dataSource.getConnection()){
|
|
try (Connection connection = dataSource.getConnection()){
|
|
connection.setAutoCommit(false);
|
|
connection.setAutoCommit(false);
|
|
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
|
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
|
|
|
+ logger.info("sql:{}",sql);
|
|
setParameters(ps, params);// 设置 SQL 语句的参数,防止 SQL 注入
|
|
setParameters(ps, params);// 设置 SQL 语句的参数,防止 SQL 注入
|
|
int executedUpdate = ps.executeUpdate();// 执行更新操作
|
|
int executedUpdate = ps.executeUpdate();// 执行更新操作
|
|
connection.commit();// 提交事务
|
|
connection.commit();// 提交事务
|
|
@@ -683,24 +705,73 @@ public class JdbcExecutor {
|
|
try {
|
|
try {
|
|
//优先使用正则表达式检测非法SQL关键词
|
|
//优先使用正则表达式检测非法SQL关键词
|
|
Matcher matcher = SQL_INJECTION_PATTERN.matcher(sql);
|
|
Matcher matcher = SQL_INJECTION_PATTERN.matcher(sql);
|
|
- if(matcher.find()) return true;
|
|
|
|
|
|
+ if(matcher.find()) {
|
|
|
|
+ logger.info("SQL_INJECTION_PATTERN.matcher(sql); 692");
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
// 使用CCJSqlParser解析SQL语句,以便进行进一步的检查
|
|
// 使用CCJSqlParser解析SQL语句,以便进行进一步的检查
|
|
Statement stmt = CCJSqlParserUtil.parse(sql);
|
|
Statement stmt = CCJSqlParserUtil.parse(sql);
|
|
// 检查解析后的SQL语句是否包含危险操作
|
|
// 检查解析后的SQL语句是否包含危险操作
|
|
boolean isInject = containsDangerousOperation(stmt,type);
|
|
boolean isInject = containsDangerousOperation(stmt,type);
|
|
// 如果包含危险操作,则将该SQL语句添加到白名单缓存中,避免重复检测
|
|
// 如果包含危险操作,则将该SQL语句添加到白名单缓存中,避免重复检测
|
|
if (!isInject) {
|
|
if (!isInject) {
|
|
|
|
+ logger.info("containsDangerousOperation(stmt,type); 701");
|
|
safeSqlCache.put(sql, true); // 白名单缓存
|
|
safeSqlCache.put(sql, true); // 白名单缓存
|
|
}
|
|
}
|
|
// 返回检测结果
|
|
// 返回检测结果
|
|
return isInject;
|
|
return isInject;
|
|
} catch (JSQLParserException e) {
|
|
} catch (JSQLParserException e) {
|
|
// 如果SQL解析失败,记录警告日志并认为该SQL语句疑似注入
|
|
// 如果SQL解析失败,记录警告日志并认为该SQL语句疑似注入
|
|
|
|
+ logger.error("抛出异常:\n",e);
|
|
logger.warn("SQL 解析失败:{};疑似注入: {}",e.getMessage(), sql);
|
|
logger.warn("SQL 解析失败:{};疑似注入: {}",e.getMessage(), sql);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 自定义sql验证
|
|
|
|
+ * @param sql
|
|
|
|
+ * @param type
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public boolean customerHandelSqlInjection(String sql,String type) {
|
|
|
|
+ if (sql == null || sql.isEmpty()) return false;
|
|
|
|
+ if(type == null || type.isEmpty()) {
|
|
|
|
+ logger.warn("未指定数据操作类型type");
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ // 首先尝试从缓存中获取该SQL语句的安全性信息
|
|
|
|
+ Boolean cached = safeSqlCache.getIfPresent(sql);
|
|
|
|
+ // 如果缓存中存在且值为false,则说明该SQL语句已被标记为不安全,直接返回false
|
|
|
|
+ if (Boolean.TRUE.equals(cached)) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ //优先使用正则表达式检测非法SQL关键词
|
|
|
|
+ // todo 暂且注释掉,自定义sql被拦住了
|
|
|
|
+ /*Matcher matcher = SQL_INJECTION_PATTERN.matcher(sql);
|
|
|
|
+ if(matcher.find()) {
|
|
|
|
+ logger.info("SQL_INJECTION_PATTERN.matcher(sql); 692");
|
|
|
|
+ return true;
|
|
|
|
+ }*/
|
|
|
|
+ // 使用CCJSqlParser解析SQL语句,以便进行进一步的检查
|
|
|
|
+ Statement stmt = CCJSqlParserUtil.parse(sql);
|
|
|
|
+ // 检查解析后的SQL语句是否包含危险操作
|
|
|
|
+ boolean isInject = containsDangerousOperation(stmt,type);
|
|
|
|
+ // 如果包含危险操作,则将该SQL语句添加到白名单缓存中,避免重复检测
|
|
|
|
+ if (!isInject) {
|
|
|
|
+ safeSqlCache.put(sql, true); // 白名单缓存
|
|
|
|
+ }
|
|
|
|
+ // 返回检测结果
|
|
|
|
+ return isInject;
|
|
|
|
+ } catch (JSQLParserException e) {
|
|
|
|
+ // 如果SQL解析失败,记录警告日志并认为该SQL语句疑似注入
|
|
|
|
+ logger.error("抛出异常:\n",e);
|
|
|
|
+ logger.warn("SQL 解析失败:{};疑似注入: {}",e.getMessage(), sql);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
/**
|
|
/**
|
|
* 判断 SQL 是否包含危险操作
|
|
* 判断 SQL 是否包含危险操作
|
|
*/
|
|
*/
|