若依数据隔离 ${params.dataScope} 替换 优化为sql 替换
安全问题:有风险的SQL查询:MyBatis解决
若依框架的数据隔离是通过 ${params.dataScope} 实现的
但是在代码安全扫描的时候$ 符会提示有风险的SQL查询:MyBatis
所以我们这里需要进行优化
参考:
MyBatis-Plus实现动态表名
MyBatis-Plus实现动态表名只能实现表名替换 也就是除了from 后面的$符号都替换不了
所以我们需要进行改造
导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
RequestDataHelper
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import java.util.Map;
public class RequestDataHelper {
/**
* 请求参数存取
*/
private static final ThreadLocal<Map<String, Object>> REQUEST_DATA = new ThreadLocal<>();
/**
* 设置请求参数
*
* @param requestData 请求参数 MAP 对象
*/
public static void setRequestData(Map<String, Object> requestData) {
REQUEST_DATA.set(requestData);
}
/**
* 获取请求参数
*
* @param param 请求参数
* @return 请求参数 MAP 对象
*/
public static <T> T getRequestData(String param) {
Map<String, Object> dataMap = getRequestData();
if (CollectionUtils.isNotEmpty(dataMap)) {
return (T) dataMap.get(param);
}
return null;
}
/**
* 获取请求参数
*
* @return 请求参数 MAP 对象
*/
public static Map<String, Object> getRequestData() {
return REQUEST_DATA.get();
}
}
MybatisPlusConfig
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class MybatisPlusConfig {
static List<String> tableList(){
List<String> tables = new ArrayList<>();
//表名 C55EA8171877E962E08DFF63AA367884123 可以为任意字符 建议复杂度高点 如果重复 会造成替换bug
tables.add("C55EA8171877E962E08DFF63AA367884123");
return tables;
}
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@PostConstruct
public void addMyInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// mybatis-plus DynamicTableNameInnerInterceptor 只能实现表名替换 所以这里我们优化了使用我们自己的拦截器LizzMybatisIntercepts
// DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
// dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
// String newTable = null;
// for (String table : tableList()) {
// newTable = RequestDataHelper.getRequestData(table);
// if (table.equals(tableName) && newTable!=null){
// tableName = newTable;
// break;
// }
// }
// return tableName;
// });
// mybatis-plus DynamicTableNameInnerInterceptor 只能实现表名替换 所以这里我们优化了使用我们自己的拦截器LizzMybatisIntercepts
LizzMybatisIntercepts lizzMybatisIntercepts = new LizzMybatisIntercepts();
interceptor.addInnerInterceptor(lizzMybatisIntercepts);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
}
LizzMybatisIntercepts
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
public class LizzMybatisIntercepts implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
log.info("#####beforeQuery");
String sql = boundSql.getSql();
// 这里是获取到需要替换到的sql 通过 PluginUtils.mpBoundSql(boundSql); 替换 mybatis-plus表名替换源码的原理也是这个
String params = RequestDataHelper.getRequestData("C55EA8171877E962E08DFF63AA367884123");
String param = sql.replaceAll("C55EA8171877E962E08DFF63AA367884123", params);
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
mpBs.sql(param);
}
}
测试
mapper
<select id="getList" resultType="com.wys.entity.User">
SELECT *
FROM user where name=#{name} and C55EA8171877E962E08DFF63AA367884123
</select>
UserController
@GetMapping("/listUser")
public List listUser(){
RequestDataHelper.setRequestData(new HashMap<String, Object>() {{
put("C55EA8171877E962E08DFF63AA367884123", "1=1");
}});
User user=new User();
user.setName("111111");
List list = userMapper.getList(user);
return list;
}
若依代码替换如下
相当于我们手动从实体类拿出来需要替换的sql 然后塞到我们的拦截器里面 有拦截器去替换
// 若依数据隔离 ${params.dataScope} 替换 优化sql 替换
String dataScope="";
Map<String, Object> params = role.getParams();
if (params!=null && params.size()>0){
dataScope = params.get("dataScope").toString();
}
String finalDataScope = dataScope;
RequestDataHelper.setRequestData(new HashMap<String, Object>() {{
put("C55EA8171877E962E08DFF63AA367884123", finalDataScope);
}});
修改前后对比图
执行sql打印
可以看到 我们的 sql可以正确替换