#{}如何防止SQL注入的?它的底层原理是什么?_sql #

@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 userList = userDao.findByUsername(“%小%”);
    List 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();
}


查看执行结果:


![](https://img-blog.csdnimg.cn/img_convert/a3e258ee37b47eb45ab268c4f5342b5f.png)


发现都能够查询出来


1.2 SQL注入问题


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


我们做一个测试:



List userList2 = userDao.findByUsername2(" aaa’ or 1=1 – ");

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

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


查询生成的SQL语句:


![](https://img-blog.csdnimg.cn/img_convert/e9f09147797870985417d685cf2da327.png)


我们传递的参数是aaa' or 1=1 --,导致查询出来了全部的数据。


大家可以想象一下,如果我是要根据id删除呢?



delete from user where id=‘${value}’


如果我传递的是:1' or 1=1; --,结果会是什么样,我想大家应该已经知道了。



> 
> 我这里id是Integer类型,不好测试,就不带大家测试了,大家有兴趣可以自己私下测试。
> 
> 
> 


如果上面使用的是`#{}`就不会出现SQL注入的问题了


![](https://img-blog.csdnimg.cn/img_convert/2ec3cde8bf0655ea5417855b0d439a37.png)


1.3 `${}`和`#{}`的区别


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


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


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


![](https://img-blog.csdnimg.cn/img_convert/da2068fddd9856c931a520e151a25dbc.png)



String abc=“123”;

#{abc}=“123”

${value}=123;


1.4 `#{}`底层是如何防止SQL注入的?


1.4.1 网上的答案


网上关于这类问题非常多,总结出来就两个原因:


**1)`#{}`底层采用的是PreparedStatement,会预编译,因此不会产生SQL注入问题;**


其实预编译是MySQL自己本身的功能,和PreparedStatement没关系;而且预编译也不是咱们理解的那个预编译,再者PreparedStatement底层默认根本没有用到预编译(要我们手动开启)!详细往下看


**2)`#{}`不会产生字符串拼接,`${}`会产生字符串拼接,因此`${}`会出现SQL注入问题;**


这两个答案都经不起深究,最终答案也只是停留在表面,也没人知道具体是为什么。


1.4.2 为什么能防止SQL注入?


我们翻开MySQL驱动的源码一看究竟;


打开PreparedStatement类的setString()方法(MyBatis在`#{}`传递参数时,是借助setString()方法来完成,`${}`则不是):


![](https://img-blog.csdnimg.cn/img_convert/a6c393020b2ed2e1cbc868d542eb978d.png)


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© {
                        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©;
                                cbuf.position(0);
                                this.charsetEncoder.encode(cbuf, bbuf, true);
                                if (bbuf.get(0) == 92) {
                                    buf.append(‘\’);
                                }
                            }

buf.append©;
                            break;
                        default:
                            buf.append©;
                        }
                    }

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;
            }

}
    }


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


![](https://img-blog.csdnimg.cn/img_convert/e8a97e476e1d5c4ba91f1984117ce301.png)


最终传递的参数如下:


![](https://img-blog.csdnimg.cn/img_convert/e85268d0538b53b907cf84e642d4fc85.png)


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


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



select * from user where username like ‘aaa’ or 1=1 – ’


![](https://img-blog.csdnimg.cn/img_convert/9692adb53a2af887dacb01a5629e6044.png)


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



select * from user where username like ‘aaa’ or 1=1 – ’


![](https://img-blog.csdnimg.cn/img_convert/7355a91010d025251e46441377bad95a.png)


我们也可以通过MySQL的日志来观察`#{}`和`${}`产生的SQL语句来分析问题:


**1)开启MySQL日志:**


在MySQL配置文件中的[mysqld]下增加如下配置:



# 是否开启mysql日志  0:关闭(默认值) 1:开启
general-log=1

# mysql 日志的存放位置
general_log_file=“D:/query.log”


![](https://img-blog.csdnimg.cn/img_convert/c0237eec875aa36f3e99ad73bc26c350.png)


**2)重启MySQL服务(要以管理员身份运行):**


![](https://img-blog.csdnimg.cn/img_convert/654b0905130dc1ac202759c67756836d.png)



net stop mysql

net start mysql


使用mybatis分别执行如下两条SQL语句:


![](https://img-blog.csdnimg.cn/img_convert/4947bfabe2a90dcab037373fee5c2d85.png)


查看MySQL日志:


![](https://img-blog.csdnimg.cn/img_convert/98c22aeed45450b53fcd63a692d97676.png)


1.5 `#{}`和`${}`的应用场景


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


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


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


例如现在要进行模糊查询,查询user表中姓张的所有员工的信息


sql语句为:`select * from user where name like '张%'`


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


如果使用`${}`:`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 '张'"%"`


**如果传入的参数是 “张%”**


使用`#{}`:`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语句我们能够发现`#{}`是会加上双引号,而`${}`匹配的是真实的值。


还有一点就是如果使用`${}`的话,里面必须要填value,即:`${value}`,`#{}`则随意


1.5.2 什么情况下用`${}`?
  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值