From 748331d64985e3392728f147d7c6db26325f18c2 Mon Sep 17 00:00:00 2001 From: EightMonth Date: Tue, 22 Apr 2025 16:00:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86jsqlparser=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jeecg-system-biz/pom.xml | 20 +- .../impl/DictTableWhiteListHandlerImpl.java | 95 ++++--- .../JimuDragExternalServiceImpl.java | 248 +++++++++--------- .../jimureport/JimuReportTokenService.java | 214 +++++++-------- 4 files changed, 303 insertions(+), 274 deletions(-) diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/pom.xml b/jeecg-boot/jeecg-module-system/jeecg-system-biz/pom.xml index 84209ffe2..2b5967cc6 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/pom.xml +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/pom.xml @@ -19,7 +19,7 @@ org.hibernate hibernate-core - @@ -30,19 +30,31 @@ weixin4j - org.jeecgframework.jimureport jimubi-spring-boot3-starter - --> + + + com.github.jsqlparser + jsqlparser + + + diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/firewall/SqlInjection/impl/DictTableWhiteListHandlerImpl.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/firewall/SqlInjection/impl/DictTableWhiteListHandlerImpl.java index b26dad02d..47003b5a4 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/firewall/SqlInjection/impl/DictTableWhiteListHandlerImpl.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/firewall/SqlInjection/impl/DictTableWhiteListHandlerImpl.java @@ -1,6 +1,11 @@ package org.jeecg.config.firewall.SqlInjection.impl; import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.exception.JeecgSqlInjectionException; import org.jeecg.common.util.oConvertUtils; @@ -12,8 +17,11 @@ import org.jeecg.config.firewall.interceptor.LowCodeModeInterceptor; import org.jeecg.modules.system.entity.SysTableWhiteList; import org.jeecg.modules.system.security.DictQueryBlackListHandler; import org.jeecg.modules.system.service.ISysTableWhiteListService; +import org.jeecgframework.minidao.sqlparser.AbstractSqlProcessor; +import org.jeecgframework.minidao.sqlparser.impl.JsqlparserSqlProcessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.jeecgframework.minidao.util.MiniDaoUtil; import java.net.URLDecoder; import java.util.*; @@ -63,34 +71,42 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler @Override public boolean isPassBySql(String sql) { -// Map parsedMap = null; -// try { -// parsedMap = JSqlParserUtils.parseAllSelectTable(sql); -// } catch (Exception e) { -// log.warn("校验sql语句,解析报错:{}", e.getMessage()); -// } -// // 如果sql有问题,则肯定执行不了,所以直接返回true -// if (parsedMap == null) { -// return true; -// } -// log.info("获取select sql信息 :{} ", parsedMap); -// // 遍历当前sql中的所有表名,如果有其中一个表或表的字段不在白名单中,则不通过 -// for (Map.Entry entry : parsedMap.entrySet()) { -// SelectSqlInfo sqlInfo = entry.getValue(); -// if (sqlInfo.isSelectAll()) { -// log.warn("查询语句中包含 * 字段,暂时先通过"); -// continue; -// } -// Set queryFields = sqlInfo.getAllRealSelectFields(); -// // 校验表名和字段是否允许查询 -// String tableName = entry.getKey(); -// if (!this.checkWhiteList(tableName, queryFields)) { -// return false; -// } -// } + Select select = null; + + try { + select = (Select) CCJSqlParserUtil.parse(sql, (parser) -> { + parser.withSquareBracketQuotation(true); + }); + } catch (JSQLParserException var10) { + JSQLParserException jsqlParserException = var10; + jsqlParserException.printStackTrace(); + } + + String tableName = ((Table)((PlainSelect)select.getSelectBody()).getFromItem()).getName(); + + List> parsedMap = null; + try { + parsedMap = MiniDaoUtil.parseSqlFields(sql); + } catch (Exception e) { + log.warn("校验sql语句,解析报错:{}", e.getMessage()); + } + // 如果sql有问题,则肯定执行不了,所以直接返回true + if (parsedMap == null) { + return true; + } + log.info("获取select sql信息 :{} ", parsedMap); + // 遍历当前sql中的所有表名,如果有其中一个表或表的字段不在白名单中,则不通过 + if (!this.checkWhiteList(tableName, parsedMap.get(0).keySet())) { + return false; + } return true; } + public static void main(String[] args) { + String sql = "select id,name,page from dual;"; + System.out.println(MiniDaoUtil.parseSqlFields(sql)); + } + @Override public boolean isPassByDict(String dictCodeString) { if (oConvertUtils.isEmpty(dictCodeString)) { @@ -120,21 +136,22 @@ public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler if (oConvertUtils.isEmpty(tableName)) { return true; } -// if (fields == null || fields.length == 0) { -// fields = new String[]{"*"}; -// } -// String sql = "select " + String.join(",", fields) + " from " + tableName; -// log.info("字典拼接的查询SQL:{}", sql); -// try { -// // 进行SQL解析 + if (fields == null || fields.length == 0) { + fields = new String[]{"*"}; + } + String sql = "select " + String.join(",", fields) + " from " + tableName; + log.info("字典拼接的查询SQL:{}", sql); + try { + // 进行SQL解析 + MiniDaoUtil.parseSqlFields(sql); // JSqlParserUtils.parseSelectSqlInfo(sql); -// } catch (Exception e) { -// // 如果SQL解析失败,则通过字段名和表名进行校验 -// return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields))); -// } -// // 通过SQL解析进行校验,可防止SQL注入 -// return this.isPassBySql(sql); - return true; + } catch (Exception e) { + // 如果SQL解析失败,则通过字段名和表名进行校验 + return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields))); + } + // 通过SQL解析进行校验,可防止SQL注入 + return this.isPassBySql(sql); +// return true; } /** diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuDragExternalServiceImpl.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuDragExternalServiceImpl.java index 1e40a53db..eb865bb5b 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuDragExternalServiceImpl.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuDragExternalServiceImpl.java @@ -1,124 +1,124 @@ -//package org.jeecg.config.jimureport; -// -//import com.alibaba.fastjson.JSONObject; -//import lombok.extern.slf4j.Slf4j; -//import org.jeecg.common.api.dto.LogDTO; -//import org.jeecg.common.system.api.ISysBaseAPI; -//import org.jeecg.common.system.vo.DictModel; -//import org.jeecg.common.util.oConvertUtils; -//import org.jeecg.modules.base.service.BaseCommonService; -//import org.jeecg.modules.drag.service.IOnlDragExternalService; -//import org.jeecg.modules.drag.vo.DragDictModel; -//import org.jeecg.modules.drag.vo.DragLogDTO; -//import org.springframework.beans.BeanUtils; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.context.annotation.Lazy; -//import org.springframework.stereotype.Service; -//import org.springframework.util.CollectionUtils; -// -//import java.util.ArrayList; -//import java.util.HashMap; -//import java.util.List; -//import java.util.Map; -// -///** -// * @Description: 字典处理 -// * @Author: lsq -// * @Date:2023-01-09 -// * @Version:V1.0 -// */ -//@Slf4j -//@Service("onlDragExternalServiceImpl") -//public class JimuDragExternalServiceImpl implements IOnlDragExternalService { -// -// @Autowired -// @Lazy -// private BaseCommonService baseCommonService; -// -// @Autowired -// @Lazy -// private ISysBaseAPI sysBaseApi; -// /** -// * 根据多个字典code查询多个字典项 -// * @param codeList -// * @return key = dictCode ; value=对应的字典项 -// */ -// @Override -// public Map> getManyDictItems(List codeList, List tableDictList) { -// Map> manyDragDictItems = new HashMap<>(); -// if(!CollectionUtils.isEmpty(codeList)){ -// Map> dictItemsMap = sysBaseApi.getManyDictItems(codeList); -// dictItemsMap.forEach((k,v)->{ -// List dictItems = new ArrayList<>(); -// v.forEach(dictItem->{ -// DragDictModel dictModel = new DragDictModel(); -// BeanUtils.copyProperties(dictItem,dictModel); -// dictItems.add(dictModel); -// }); -// manyDragDictItems.put(k,dictItems); -// }); -// } -// -// if(!CollectionUtils.isEmpty(tableDictList)){ -// tableDictList.forEach(item->{ -// List dictItems = new ArrayList<>(); -// JSONObject object = JSONObject.parseObject(item.toString()); -// String dictField = object.getString("dictField"); -// String dictTable = object.getString("dictTable"); -// String dictText = object.getString("dictText"); -// String fieldName = object.getString("fieldName"); -// List dictItemsList = sysBaseApi.queryTableDictItemsByCode(dictTable,dictText,dictField); -// dictItemsList.forEach(dictItem->{ -// DragDictModel dictModel = new DragDictModel(); -// BeanUtils.copyProperties(dictItem,dictModel); -// dictItems.add(dictModel); -// }); -// manyDragDictItems.put(fieldName,dictItems); -// }); -// } -// return manyDragDictItems; -// } -// -// /** -// * -// * @param dictCode -// * @return -// */ -// @Override -// public List getDictItems(String dictCode) { -// List dictItems = new ArrayList<>(); -// if(oConvertUtils.isNotEmpty(dictCode)){ -// List dictItemsList = sysBaseApi.getDictItems(dictCode); -// dictItemsList.forEach(dictItem->{ -// DragDictModel dictModel = new DragDictModel(); -// BeanUtils.copyProperties(dictItem,dictModel); -// dictItems.add(dictModel); -// }); -// } -// return dictItems; -// } -// -// /** -// * 添加日志 -// * @param dragLogDTO -// */ -// @Override -// public void addLog(DragLogDTO dragLogDTO) { -// if(oConvertUtils.isNotEmpty(dragLogDTO)){ -// LogDTO dto = new LogDTO(); -// BeanUtils.copyProperties(dragLogDTO,dto); -// baseCommonService.addLog(dto); -// } -// } -// -// /** -// * 保存日志 -// * @param logMsg -// * @param logType -// * @param operateType -// */ -// @Override -// public void addLog(String logMsg, int logType, int operateType) { -// baseCommonService.addLog(logMsg,logType,operateType); -// } -//} \ No newline at end of file +package org.jeecg.config.jimureport; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.api.dto.LogDTO; +import org.jeecg.common.system.api.ISysBaseAPI; +import org.jeecg.common.system.vo.DictModel; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.modules.base.service.BaseCommonService; +import org.jeecg.modules.drag.service.IOnlDragExternalService; +import org.jeecg.modules.drag.vo.DragDictModel; +import org.jeecg.modules.drag.vo.DragLogDTO; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Description: 字典处理 + * @Author: lsq + * @Date:2023-01-09 + * @Version:V1.0 + */ +@Slf4j +@Service("onlDragExternalServiceImpl") +public class JimuDragExternalServiceImpl implements IOnlDragExternalService { + + @Autowired + @Lazy + private BaseCommonService baseCommonService; + + @Autowired + @Lazy + private ISysBaseAPI sysBaseApi; + /** + * 根据多个字典code查询多个字典项 + * @param codeList + * @return key = dictCode ; value=对应的字典项 + */ + @Override + public Map> getManyDictItems(List codeList, List tableDictList) { + Map> manyDragDictItems = new HashMap<>(); + if(!CollectionUtils.isEmpty(codeList)){ + Map> dictItemsMap = sysBaseApi.getManyDictItems(codeList); + dictItemsMap.forEach((k,v)->{ + List dictItems = new ArrayList<>(); + v.forEach(dictItem->{ + DragDictModel dictModel = new DragDictModel(); + BeanUtils.copyProperties(dictItem,dictModel); + dictItems.add(dictModel); + }); + manyDragDictItems.put(k,dictItems); + }); + } + + if(!CollectionUtils.isEmpty(tableDictList)){ + tableDictList.forEach(item->{ + List dictItems = new ArrayList<>(); + JSONObject object = JSONObject.parseObject(item.toString()); + String dictField = object.getString("dictField"); + String dictTable = object.getString("dictTable"); + String dictText = object.getString("dictText"); + String fieldName = object.getString("fieldName"); + List dictItemsList = sysBaseApi.queryTableDictItemsByCode(dictTable,dictText,dictField); + dictItemsList.forEach(dictItem->{ + DragDictModel dictModel = new DragDictModel(); + BeanUtils.copyProperties(dictItem,dictModel); + dictItems.add(dictModel); + }); + manyDragDictItems.put(fieldName,dictItems); + }); + } + return manyDragDictItems; + } + + /** + * + * @param dictCode + * @return + */ + @Override + public List getDictItems(String dictCode) { + List dictItems = new ArrayList<>(); + if(oConvertUtils.isNotEmpty(dictCode)){ + List dictItemsList = sysBaseApi.getDictItems(dictCode); + dictItemsList.forEach(dictItem->{ + DragDictModel dictModel = new DragDictModel(); + BeanUtils.copyProperties(dictItem,dictModel); + dictItems.add(dictModel); + }); + } + return dictItems; + } + + /** + * 添加日志 + * @param dragLogDTO + */ + @Override + public void addLog(DragLogDTO dragLogDTO) { + if(oConvertUtils.isNotEmpty(dragLogDTO)){ + LogDTO dto = new LogDTO(); + BeanUtils.copyProperties(dragLogDTO,dto); + baseCommonService.addLog(dto); + } + } + + /** + * 保存日志 + * @param logMsg + * @param logType + * @param operateType + */ + @Override + public void addLog(String logMsg, int logType, int operateType) { + baseCommonService.addLog(logMsg,logType,operateType); + } +} \ No newline at end of file diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuReportTokenService.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuReportTokenService.java index 279d94708..3fe54d1ad 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuReportTokenService.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuReportTokenService.java @@ -1,107 +1,107 @@ -//package org.jeecg.config.jimureport; -// -//import lombok.extern.slf4j.Slf4j; -//import org.jeecg.common.system.util.JwtUtil; -//import org.jeecg.common.system.vo.SysUserCacheInfo; -//import org.jeecg.common.util.RedisUtil; -//import org.jeecg.common.util.TokenUtils; -//import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; -//import org.jeecg.modules.system.service.impl.SysBaseApiImpl; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.context.annotation.Lazy; -//import org.springframework.stereotype.Component; -//import org.springframework.util.CollectionUtils; -// -//import jakarta.servlet.http.HttpServletRequest; -//import java.util.HashMap; -//import java.util.Map; -//import java.util.Set; -// -///** -// * 自定义积木报表鉴权(如果不进行自定义,则所有请求不做权限控制) -// * * 1.自定义获取登录token -// * * 2.自定义获取登录用户 -// * @author: jeecg-boot -// */ -// -// -//@Slf4j -//@Component -//public class JimuReportTokenService implements JmReportTokenServiceI { -// @Autowired -// private SysBaseApiImpl sysBaseApi; -// @Autowired -// @Lazy -// private RedisUtil redisUtil; -// -// @Override -// public String getToken(HttpServletRequest request) { -// return TokenUtils.getTokenByRequest(request); -// } -// -// @Override -// public String getUsername(String token) { -// return JwtUtil.getUsername(token); -// } -// -// @Override -// public String[] getRoles(String token) { -// String username = JwtUtil.getUsername(token); -// Set roles = sysBaseApi.getUserRoleSet(username); -// if(CollectionUtils.isEmpty(roles)){ -// return null; -// } -// return (String[]) roles.toArray(new String[roles.size()]); -// } -// -// @Override -// public Boolean verifyToken(String token) { -// return TokenUtils.verifyToken(token, sysBaseApi, redisUtil); -// } -// -// @Override -// public Map getUserInfo(String token) { -// Map map = new HashMap(5); -// String username = JwtUtil.getUsername(token); -// //此处通过token只能拿到一个信息 用户账号 后面的就是根据账号获取其他信息 查询数据或是走redis 用户根据自身业务可自定义 -// SysUserCacheInfo userInfo = null; -// try { -// userInfo = sysBaseApi.getCacheUser(username); -// } catch (Exception e) { -// log.error("获取用户信息异常:"+ e.getMessage()); -// return map; -// } -// //设置账号名 -// map.put(SYS_USER_CODE, userInfo.getSysUserCode()); -// //设置部门编码 -// map.put(SYS_ORG_CODE, userInfo.getSysOrgCode()); -// // 将所有信息存放至map 解析sql/api会根据map的键值解析 -// return map; -// } -// -// /** -// * 将jeecgboot平台的权限传递给积木报表 -// * @param token -// * @return -// */ -// @Override -// public String[] getPermissions(String token) { -// // 获取用户信息 -// String username = JwtUtil.getUsername(token); -// SysUserCacheInfo userInfo = null; -// try { -// userInfo = sysBaseApi.getCacheUser(username); -// } catch (Exception e) { -// log.error("获取用户信息异常:"+ e.getMessage()); -// } -// if(userInfo == null){ -// return null; -// } -// // 查询权限 -// Set userPermissions = sysBaseApi.getUserPermissionSet(userInfo.getSysUserId()); -// if(CollectionUtils.isEmpty(userPermissions)){ -// return null; -// } -// return userPermissions.toArray(new String[0]); -// } -//} +package org.jeecg.config.jimureport; + +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.system.util.JwtUtil; +import org.jeecg.common.system.vo.SysUserCacheInfo; +import org.jeecg.common.util.RedisUtil; +import org.jeecg.common.util.TokenUtils; +import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; +import org.jeecg.modules.system.service.impl.SysBaseApiImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * 自定义积木报表鉴权(如果不进行自定义,则所有请求不做权限控制) + * * 1.自定义获取登录token + * * 2.自定义获取登录用户 + * @author: jeecg-boot + */ + + +@Slf4j +@Component +public class JimuReportTokenService implements JmReportTokenServiceI { + @Autowired + private SysBaseApiImpl sysBaseApi; + @Autowired + @Lazy + private RedisUtil redisUtil; + + @Override + public String getToken(HttpServletRequest request) { + return TokenUtils.getTokenByRequest(request); + } + + @Override + public String getUsername(String token) { + return JwtUtil.getUsername(token); + } + + @Override + public String[] getRoles(String token) { + String username = JwtUtil.getUsername(token); + Set roles = sysBaseApi.getUserRoleSet(username); + if(CollectionUtils.isEmpty(roles)){ + return null; + } + return (String[]) roles.toArray(new String[roles.size()]); + } + + @Override + public Boolean verifyToken(String token) { + return TokenUtils.verifyToken(token, sysBaseApi, redisUtil); + } + + @Override + public Map getUserInfo(String token) { + Map map = new HashMap(5); + String username = JwtUtil.getUsername(token); + //此处通过token只能拿到一个信息 用户账号 后面的就是根据账号获取其他信息 查询数据或是走redis 用户根据自身业务可自定义 + SysUserCacheInfo userInfo = null; + try { + userInfo = sysBaseApi.getCacheUser(username); + } catch (Exception e) { + log.error("获取用户信息异常:"+ e.getMessage()); + return map; + } + //设置账号名 + map.put(SYS_USER_CODE, userInfo.getSysUserCode()); + //设置部门编码 + map.put(SYS_ORG_CODE, userInfo.getSysOrgCode()); + // 将所有信息存放至map 解析sql/api会根据map的键值解析 + return map; + } + + /** + * 将jeecgboot平台的权限传递给积木报表 + * @param token + * @return + */ + @Override + public String[] getPermissions(String token) { + // 获取用户信息 + String username = JwtUtil.getUsername(token); + SysUserCacheInfo userInfo = null; + try { + userInfo = sysBaseApi.getCacheUser(username); + } catch (Exception e) { + log.error("获取用户信息异常:"+ e.getMessage()); + } + if(userInfo == null){ + return null; + } + // 查询权限 + Set userPermissions = sysBaseApi.getUserPermissionSet(userInfo.getSysUserId()); + if(CollectionUtils.isEmpty(userPermissions)){ + return null; + } + return userPermissions.toArray(new String[0]); + } +}