MyBaits中#{}如何防止注入

一、MyBatis中${}和#{}的区别

1.1 ${}和#{}演示

数据库数据:

数据

dao接口:

List<User> findByUsername(String username);

List<User> findByUsername2(String username);

Mapper.xml:

<!-- 使用#{} -->
<select id="findByUsername" parameterType="java.lang.String" resultType="com.lscl.entity.User">
    select * from user where username like #{username}
</select>

<!-- 使用${},注意${}中的值必须要填value -->
<select id="findByUsername2" parameterType="java.lang.String" resultType="com.lscl.entity.User">
    select * from user where username like '%${value}%'
</select>

执行测试代码:

@Test
public void findByUsername() throws Exception {
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

    SqlSessionFactory factory = builder.build(in);

    // true:自动提交
    SqlSession session = factory.openSession(true);

    UserDao userDao = session.getMapper(UserDao.class);

    List<User> userList = userDao.findByUsername("%小%");
    List<User> userList2 = userDao.findByUsername2("小");

    System.out.println("userList: ");
    for (User user : userList) {
        System.out.println(user);
    }

    System.out.println("userList2: ");

    for (User user : userList2) {
        System.out.println(user);
    }

    session.close();
    in.close();
}

查看执行结果:

查看执行结果

发现都能够查询出来

1.2 SQL注入问题

${}会产生SQL注入,#{}不会产生SQL注入问题

我们做一个测试:

List<User> userList2 = userDao.findByUsername2(" aaa' or 1=1 -- ");

System.out.println("userList2: ");

for (User user : userList2) {
    System.out.println(user);
}

查询生成的SQL语句:


查询生成的SQL语句

我们传递的参数是aaa' or 1=1 --,导致查询出来了全部的数据。大家可以想象一下,如果我是要根据id删除呢?

delete from user where id='${value}'

如果我传递的是:1' or 1=1; --,结果会是什么样,我想大家应该已经知道了。如果上面使用的是#{}就不会出现SQL注入的问题了

使用#{}
1.3 ${}和#{}的区别

#{}匹配的是一个占位符,相当于JDBC中的一个?,会对一些敏感的字符进行过滤,编译过后会对传递的值加上双引号,因此可以防止SQL注入问题。

${}匹配的是真实传递的值,传递过后,会与sql语句进行字符串拼接。${}会与其他sql进行字符串拼接,不能预防sql注入问题。

查看#{}和${}生成的SQL语句

查看#{}和${}生成的SQL语句
String abc=“123”;

#{abc}="123"

${value}=123;
1.4 为什么能防止SQL注入?

我们翻开MySQL驱动的源码一看究竟;打开PreparedStatement类的setString()方法(MyBatis在#{}传递参数时,是借助setString()方法来完成,${}则不是):

PreparedStatement

setString()方法全部源码:

public void setString(int parameterIndex, String x) throws SQLException {
        synchronized(this.checkClosed().getConnectionMutex()) {
            if (x == null) {
                this.setNull(parameterIndex, 1);
            } else {
                this.checkClosed();
                int stringLength = x.length();
                StringBuilder buf;
                if (this.connection.isNoBackslashEscapesSet()) {
                    boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);
                    Object parameterAsBytes;
                    byte[] parameterAsBytes;
                    if (!needsHexEscape) {
                        parameterAsBytes = null;
                        buf = new StringBuilder(x.length() + 2);
                        buf.append('\'');
                        buf.append(x);
                        buf.append('\'');
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(buf.toString());
                        }

                        this.setInternal(parameterIndex, parameterAsBytes);
                    } else {
                        parameterAsBytes = null;
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(x);
                        }

                        this.setBytes(parameterIndex, parameterAsBytes);
                    }

                    return;
                }

                String parameterAsString = x;
                boolean needsQuoted = true;
                if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) {
                    needsQuoted = false;
                    buf = new StringBuilder((int)((double)x.length() * 1.1D));
                    buf.append('\'');

                    for(int i = 0; i < stringLength; ++i) {  //遍历字符串,获取到每个字符
                        char c = x.charAt(i);
                        switch(c) {
                        case '\u0000':
                            buf.append('\\');
                            buf.append('0');
                            break;
                        case '\n':
                            buf.append('\\');
                            buf.append('n');
                            break;
                        case '\r':
                            buf.append('\\');
                            buf.append('r');
                            break;
                        case '\u001a':
                            buf.append('\\');
                            buf.append('Z');
                            break;
                        case '"':
                            if (this.usingAnsiMode) {
                                buf.append('\\');
                            }

                            buf.append('"');
                            break;
                        case '\'':
                            buf.append('\\');
                            buf.append('\'');
                            break;
                        case '\\':
                            buf.append('\\');
                            buf.append('\\');
                            break;
                        case '¥':
                        case '₩':
                            if (this.charsetEncoder != null) {
                                CharBuffer cbuf = CharBuffer.allocate(1);
                                ByteBuffer bbuf = ByteBuffer.allocate(1);
                                cbuf.put(c);
                                cbuf.position(0);
                                this.charsetEncoder.encode(cbuf, bbuf, true);
                                if (bbuf.get(0) == 92) {
                                    buf.append('\\');
                                }
                            }

                            buf.append(c);
                            break;
                        default:
                            buf.append(c);
                        }
                    }

                    buf.append('\'');
                    parameterAsString = buf.toString();
                }

                buf = null;
                byte[] parameterAsBytes;
                if (!this.isLoadDataQuery) {
                    if (needsQuoted) {
                        parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    } else {
                        parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    }
                } else {
                    parameterAsBytes = StringUtils.getBytes(parameterAsString);
                }

                this.setInternal(parameterIndex, parameterAsBytes);
                this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12;
            }

        }
    }

我们执行#{}的查询语句,打断点观察

断点

最终传递的参数如下

断点

最终传递的参数为:'aaa' or 1=1 --

咱们在数据库中执行如下SQL语句(肯定是查询不到数据的):

select * from user where username like 'aaa\' or 1=1 -- '
查询结果

如果把PreparedStatement加的那根"/"去掉呢?我们执行SQL试试:

select * from user where username like 'aaa' or 1=1 -- '
根"/"去掉
1.5 #{}和${}的应用场景

既然#{}${}好那么多,那为什么还要有${}这个东西存在呢?干脆都用#{}不就万事大吉吗?

其实不是的,${}也有用武之地,我们都知道${}会产生字符串拼接,来生成一个新的字符串

1.5.1 ${}和#{}用法上的区别

例如现在要进行模糊查询,查询user表中姓张的所有员工的信息
sql语句为:select * from user where name like '张%'

1、此时如果传入的参数是 “张”

如果使用${}:

select * from user where name like '${value}%'

生成的sql语句:select * from user where name like '张%'

如果使用#{}:select * from user where name like #{value}"%"

生成的sql语句:select * from user where name like '张'"%"

2、如果传入的参数是 “张%”
使用#{}:select * from user where name like #{value}

生成的sql语句:select * from user where name like '张%'

使用${}:

select * from user where name like '${value}'

生成的sql语句:select * from user where name like '张%'

通过上面的SQL语句我们能够发现#{}是会加上双引号,而${}匹配的是真实的值。

1.5.2 什么情况下用${}?

场景举例:


场景举例

代码测试:

代码测试

执行之后,发现执行成功

执行结果

我们可以切换一下,把${}改成#{},会出现SQL语法错误的异常


异常

二、总结

MyBatis的#{}之所以能够预防SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个'/'代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。

${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。

#{}在使用时,会根据传递进来的值来选择是否加上双引号,因此我们传递参数的时候一般都是直接传递,不用加双引号,${}则不会,我们需要手动加

#{}针对SQL注入进行了字符过滤,${}则只是作为普通传值,并没有考虑到这些问题

#{}的应用场景是为给SQL语句的where字句传递条件值,${}的应用场景是为了传递一些需要参与SQL语句语法生成的值。

三、项目中使用like

MyBaits中用#{}防止注入

方式一
<if test="params.searchKey != null and params.searchKey != ''">
   and client_id like concat('%', #{params.searchKey}, '%')
</if>
方式二
1、查询条件对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("serial")
@ApiModel(value = "QueryLuckyActivityVO")
public class QueryLuckyActivityVO {

    @ApiModelProperty(value = "抽奖活动ID")
    private String activityNo;

    @ApiModelProperty(value = "抽奖活动标题")
    private String activityName;

    @ApiModelProperty(value = "参与人数(大于)")
    private Integer joinNumberStart;

    @ApiModelProperty(value = "参与人数(小于)")
    private Integer joinNumberEnd;

    @ApiModelProperty(value = "状态")
    private PublishStatusEnum publishStatus;

    @ApiModelProperty(value = "活动类型(1=抽奖+兑奖;2=仅抽奖;3=仅兑奖)")
    private String activityType;

    public String getActivityNo() {
        if(StringUtil.isNotBlank(activityNo)){
            return "%"+activityNo+"%";
        }
        return activityNo;
    }

    public String getActivityName() {
        if(StringUtil.isNotBlank(activityName)){
            return "%"+activityName+"%";
        }
        return activityName;
    }
}
2、mybatis
<select id="findPage" resultType="com.alanchen.lucky.dto.LuckyActivityDTO">
        select * from   t_lucky_activity m
        <where>
            m.deleted = 0
            <if test="vo.activityNo != null and vo.activityNo!=''">
                and m.activity_no like #{vo.activityNo}
            </if>
            <if test="vo.activityName != null and vo.activityName!=''">
                and m.activity_name like #{vo.activityName}
            </if>
            <if test="vo.joinNumberStart != null">
                and m.join_number <![CDATA[ >= ]]> #{vo.joinNumberStart}
            </if>
        </where>
        order by m.create_time desc
    </select>

原文地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容