JAVAWEB中mybatis设计多条件筛选、防SQL注入

开始

1.痛点分析

项目中需要进行超多条件筛选,示例页面如下:
在这里插入图片描述
这里检索条件非常多,且是不能写死的,需要灵活多变,以应对甲方需求的灵(改)(你)(大)(爷)

2.采用框架

后台 mybatis / mysql
前端 Layui / Jquery

3.mybatis是如何防止sql注入攻击的

SQL注入攻击举例:在你的input中输入‘ 0 or id= 123 ’,则很有可能你的后台会执行 where gender = 0 or id = 123 这只是很小的例子,一般前端要做表单值验证(一般用正则表达式过滤),如果前后端都不设防,系统将有很大风险。

先贴一段mybatis的sql片段

<select id="select" resultMap="BaseResultMap" parameterType="com.core.entity.User" >
    select
    *
    from sys_user
    <where>
      <if test="id != null">
        and id = #{id,VARCHAR}
      </if>
      <if test="age != null">
        and age = #{age,INTEGER}
      </if>
      <if test="note != null">
        ${note}
      </if>
    </where>
    order by crttime
    limit ${m},${n}
  </select>

可以看到,以上SQL标签内有三个条件标签。
其中#{id,VARCHAR}以及 #{age,INTEGER}在执行时(预编译),会对输入字段进行检查并且格式化输出内容。
比如 id = #{123,VARCHAR} 输出 id = ‘123’。
而 age = #{12,INTEGER} 输出 age = 12。
也就是字符串格式会自动包含引号,而数字不会。

通俗说,
#{属性值,属性类型} 格式,是防sql注入的。
${属性值} 是直接输入,不检查和格式化的,不防sql注入。

所以,在mybatis中,使用 ${属性值} 的这种写法,是不安全的。

在实际开发中,前端是要对用户输入的字段进行格式检查的,再加上mybatis的格式检查,很大程度上防范了sql注入攻击。

4.实现

4.1 以对象传参形式将sql条件语句转换为对象。

其实解决多条件筛选最简单暴力的方法,就是在前端拼接sql,然后将sql传到后台执行查询。

历史:2010年以前的很多JAVA老项目,jsp内使用el表达式写满sql语句,密密麻麻程度令人发狂,例如:
在这里插入图片描述
这样的方式虽然简单,但却将sql句子暴露给所有人看,你的数据结构将暴露无遗,且面临sql注入攻击的风险。

为了不重蹈覆辙,这里我自写内部解析方法,进行安全的sql片段还原,这是解决多条件筛选的一个思路。

/*
*前台js代码*
*/
var _condition = [];//构造sql拼接对象
_condition.push({keywords: 'like', column: 'phone', value: '188'});//意为phone like '%188%'
_condition.push({keywords: 'in', column: 'type', values: ['normal','pause']});//意为 type in ('normal','pause')

//以下简写,请求数据,传输sql拼接对象
ajax({
	...
	url:'**/getPageData',
	data:{
		c: JSON.stringify(_condition), //将对象以json字符串形式传输,也可以考虑序列化
		m:0, //分页参数
		n:10 //分页参数
	}, 
	success:function(e){
		//获取结果集
	}
});
4.2 后台接收对象并解析成sql查询语句
//接收到前端传来请求,将json字符串转化对象并还原成sql语句

...处理接收到的数据c,其他省略...

String sql = parseCondtionSql(c);

//后台解析的方法
private String parseCondtionSql(String sqlObjectJson){
        if(isEmptyStr(sqlObjectJson)){
            return null;
        }
        StringBuilder sb = new StringBuilder();
        List<Map<String,Object>> queryList = (List<Map<String,Object>>) super.jsonToObject(sqlObjectJson, List.class);
        for(Map<String,Object> single : queryList){
            if("null".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" is null ");
            }
            if("notnull".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" is not null ");
            }
            if("eq".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" = ")
                        .append("'")
                        .append(sqlFormater(single.get("value").toString()))
                        .append("' ");
            }
            if("numeq".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" = ")
                        .append(sqlFormater(single.get("value").toString()))
                        .append(" ");
            }
            if("neq".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" != ")
                        .append("'")
                        .append(sqlFormater(single.get("value").toString()))
                        .append("' ");
            }
            if("like".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" like ")
                        .append(" '%")
                        .append(sqlFormater(single.get("value").toString()))
                        .append("%' ");
            }
            if("likes".equals(single.get("keywords"))){
                sb.append(" and ( ");
                List<String> column = (List) single.get("column");
                int index = 0;
                for(String c : column){
                    if(index != 0){
                        sb.append(" or ");
                    }
                    sb.append(sqlFormater(c))
                            .append(" like ")
                            .append(" '%")
                            .append(sqlFormater(single.get("value").toString()))
                            .append("%' ");
                    index ++;
                }
                sb.append(" ) ");
            }
            if("likeFront".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" like ")
                        .append(" '")
                        .append(sqlFormater(single.get("value").toString()))
                        .append("%' ");
            }
            if("likeBehind".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" like ")
                        .append(" '%")
                        .append(sqlFormater(single.get("value").toString()))
                        .append("' ");
            }
            if("in".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" in ( ");
                List<String> values = (List) single.get("values");
                int index = 0;
                for(String v : values){
                    if(index != 0){
                        sb.append(" , ");
                    }
                    sb.append(" '")
                            .append(sqlFormater(v))
                            .append("' ");
                }
                sb.append(" ) ");
            }
            if("range".equals(single.get("keywords"))){
                Map<String,Object> vObj = (Map<String, Object>) single.get("value");
                if(!isEmptyStr((String)vObj.get("start"))){
                    sb.append(" and ")
                            .append(sqlFormater(single.get("column").toString()))
                            .append(" >= '")
                            .append(sqlFormater(vObj.get("start").toString()))
                            .append(" 00:00:00' ");
                }
                if(!isEmptyStr((String)vObj.get("end"))){
                    sb.append(" and ")
                            .append(sqlFormater(single.get("column").toString()))
                            .append(" < '")
                            .append(sqlFormater(vObj.get("end").toString()))
                            .append(" 23:59:59' ");
                }
            }
            if("morethan".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" >= ")
                        .append(sqlFormater(single.get("value").toString()));
            }
            if("lassthan".equals(single.get("keywords"))){
                sb.append(" and ")
                        .append(sqlFormater(single.get("column").toString()))
                        .append(" <= ")
                        .append(sqlFormater(single.get("value").toString()));
            }
        }
        return sb.toString();
    }
	
	//后台尽可能防sql注入,将字段名和字段值,都去除关键符号并去除空格。
	private String sqlFormater(String value) {
        return value.replaceAll("'", "")
                .replaceAll(",", "")
                .replaceAll("=", "")
                .replaceAll(" ", "");
    }
得到转换sql片段:
" and phone like '%188%' and type in ('normal','pause') "
4.3 将sql片段插入到mapper.xml中
<select id="selectPage" resultMap="BaseResultMap" parameterType="com.core.entity.User" >
    select
    *
    from sys_user
    <where>
      <if test="sql != null">
        ${sql}  <!-- 此处是还原的sql片段 -->
      </if>
    </where>
    order by crttime
    limit ${m},${n}
</select>
最终执行sql:
select
  *
from sys_user
where 
1 = 1
and phone like '%188%' and type in ('normal','pause')
order by crttime
limit 0,10

其实原理so简单,没有什么绚丽的逻辑,就是最笨的方法。
前端想以什么条件做筛选都可以,然后组拼对象,对sql片段加以格式限制,从而在保证数据结构不被窥探的同时最大限度防止SQL注入攻击。

-end
有任何错误欢迎指正,有更好方法也请提点,谢谢。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值