<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          MyBaits中#{}和${}的真正區(qū)別,${}的使用場(chǎng)景,#{}如何防止注入?

          共 4573字,需瀏覽 10分鐘

           ·

          2022-03-01 19:21

          上一篇:最近一些想法

          來源:blog.csdn.net/Bb15070047748/article/details/107188167

          一、MyBatis中${}和#{}的區(qū)別

          1.1?${}#{}演示

          數(shù)據(jù)庫(kù)數(shù)據(jù):

          dao接口:

          List?findByUsername(String?username);

          List?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>


          <select?id="findByUsername2"?parameterType="java.lang.String"?resultType="com.lscl.entity.User">
          ????select?*?from?user?where?username?like?'%${value}%'
          select>

          執(zhí)行測(cè)試代碼:

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

          ????SqlSessionFactoryBuilder?builder?=?new?SqlSessionFactoryBuilder();

          ????SqlSessionFactory?factory?=?builder.build(in);

          ????//?true:自動(dòng)提交
          ????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();
          }

          查看執(zhí)行結(jié)果:

          發(fā)現(xiàn)都能夠查詢出來

          1.2 SQL注入問題

          ${}會(huì)產(chǎn)生SQL注入,#{}不會(huì)產(chǎn)生SQL注入問題

          我們做一個(gè)測(cè)試:

          List?userList2?=?userDao.findByUsername2("?aaa'?or?1=1?--?");

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

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

          查詢生成的SQL語(yǔ)句:

          我們傳遞的參數(shù)是aaa' or 1=1 --,導(dǎo)致查詢出來了全部的數(shù)據(jù)。

          大家可以想象一下,如果我是要根據(jù)id刪除呢?

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

          如果我傳遞的是:1' or 1=1; --,結(jié)果會(huì)是什么樣,我想大家應(yīng)該已經(jīng)知道了。

          我這里id是Integer類型,不好測(cè)試,就不帶大家測(cè)試了,大家有興趣可以自己私下測(cè)試。

          如果上面使用的是#{}就不會(huì)出現(xiàn)SQL注入的問題了

          1.3?${}#{}的區(qū)別

          #{}匹配的是一個(gè)占位符,相當(dāng)于JDBC中的一個(gè)?,會(huì)對(duì)一些敏感的字符進(jìn)行過濾,編譯過后會(huì)對(duì)傳遞的值加上雙引號(hào),因此可以防止SQL注入問題。
          ${}匹配的是真實(shí)傳遞的值,傳遞過后,會(huì)與sql語(yǔ)句進(jìn)行字符串拼接。${}會(huì)與其他sql進(jìn)行字符串拼接,不能預(yù)防sql注入問題。
          查看#{}${}生成的SQL語(yǔ)句:
          String?abc=“123”;

          #{abc}="123"

          ${value}=123;

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

          1.4.1 網(wǎng)上的答案

          網(wǎng)上關(guān)于這類問題非常多,總結(jié)出來就兩個(gè)原因:

          1)#{}底層采用的是PreparedStatement,會(huì)預(yù)編譯,因此不會(huì)產(chǎn)生SQL注入問題;

          其實(shí)預(yù)編譯是MySQL自己本身的功能,和PreparedStatement沒關(guān)系;而且預(yù)編譯也不是咱們理解的那個(gè)預(yù)編譯,再者PreparedStatement底層默認(rèn)根本沒有用到預(yù)編譯(要我們手動(dòng)開啟)!詳細(xì)往下看

          2)#{}不會(huì)產(chǎn)生字符串拼接,${}會(huì)產(chǎn)生字符串拼接,因此${}會(huì)出現(xiàn)SQL注入問題;

          這兩個(gè)答案都經(jīng)不起深究,最終答案也只是停留在表面,也沒人知道具體是為什么。

          1.4.2 為什么能防止SQL注入?

          我們翻開MySQL驅(qū)動(dòng)的源碼一看究竟;

          打開PreparedStatement類的setString()方法(MyBatis在#{}傳遞參數(shù)時(shí),是借助setString()方法來完成,${}則不是):

          setString()方法全部源碼:

          另外,搜索公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師后臺(tái)回復(fù)“2T”,獲取一份驚喜禮包。

          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?//遍歷字符串,獲取到每個(gè)字符
          ????????????????????????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;
          ????????????}

          ????????}
          ????}

          我們執(zhí)行#{}的查詢語(yǔ)句,打斷點(diǎn)觀察:

          最終傳遞的參數(shù)如下:

          最終傳遞的參數(shù)為:'aaa\' or 1=1 --

          咱們?cè)跀?shù)據(jù)庫(kù)中執(zhí)行如下SQL語(yǔ)句(肯定是查詢不到數(shù)據(jù)的):

          select?*?from?user?where?username?like?'aaa\'?or?1=1?--?'

          如果把PreparedStatement加的那根"/"去掉呢?我們執(zhí)行SQL試試:

          select?*?from?user?where?username?like?'aaa'?or?1=1?--?'

          我們也可以通過MySQL的日志來觀察#{}${}產(chǎn)生的SQL語(yǔ)句來分析問題:

          1)開啟MySQL日志:

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

          #?是否開啟mysql日志??0:關(guān)閉(默認(rèn)值)?1:開啟
          general-log=1

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

          2)重啟MySQL服務(wù)(要以管理員身份運(yùn)行):

          net?stop?mysql

          net?start?mysql

          使用mybatis分別執(zhí)行如下兩條SQL語(yǔ)句:

          查看MySQL日志:

          1.5?#{}${}的應(yīng)用場(chǎng)景

          既然#{}${}好那么多,那為什么還要有${}這個(gè)東西存在呢?干脆都用#{}不就萬(wàn)事大吉嗎?關(guān)注互聯(lián)網(wǎng)架構(gòu)師

          其實(shí)不是的,${}也有用武之地,我們都知道${}會(huì)產(chǎn)生字符串拼接,來生成一個(gè)新的字符串

          1.5.1 ${}和#{}用法上的區(qū)別

          例如現(xiàn)在要進(jìn)行模糊查詢,查詢user表中姓張的所有員工的信息

          另外,搜索公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師后臺(tái)回復(fù)“2T”,獲取一份驚喜禮包。

          sql語(yǔ)句為:select * from user where name like '張%'

          此時(shí)如果傳入的參數(shù)是 “張”

          如果使用${}select * from user where name like '${value}%'

          生成的sql語(yǔ)句:select * from user where name like '張%'

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

          生成的sql語(yǔ)句:select * from user where name like '張'"%"

          如果傳入的參數(shù)是 “張%”

          使用#{}select * from user where name like #{value}

          生成的sql語(yǔ)句:select * from user where name like '張%'

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

          生成的sql語(yǔ)句:select * from user where name like '張%'

          通過上面的SQL語(yǔ)句我們能夠發(fā)現(xiàn)#{}是會(huì)加上雙引號(hào),而${}匹配的是真實(shí)的值。

          還有一點(diǎn)就是如果使用${}的話,里面必須要填value,即:${value}#{}則隨意

          1.5.2 什么情況下用${}?

          場(chǎng)景舉例:

          代碼測(cè)試:

          執(zhí)行之后,發(fā)現(xiàn)執(zhí)行成功

          我們可以切換一下,把${}改成#{},會(huì)出現(xiàn)SQL語(yǔ)法錯(cuò)誤的異常

          1.6 總結(jié)

          1.6.1 SQL注入問題
          MyBatis的#{}之所以能夠預(yù)防SQL注入是因?yàn)榈讓邮褂昧薖reparedStatement類的setString()方法來設(shè)置參數(shù),此方法會(huì)獲取傳遞進(jìn)來的參數(shù)的每個(gè)字符,然后進(jìn)行循環(huán)對(duì)比,如果發(fā)現(xiàn)有敏感字符(如:?jiǎn)我?hào)、雙引號(hào)等),則會(huì)在前面加上一個(gè)'/'代表轉(zhuǎn)義此符號(hào),讓其變?yōu)橐粋€(gè)普通的字符串,不參與SQL語(yǔ)句的生成,達(dá)到防止SQL注入的效果。
          其次${}本身設(shè)計(jì)的初衷就是為了參與SQL語(yǔ)句的語(yǔ)法生成,自然而然會(huì)導(dǎo)致SQL注入的問題(不會(huì)考慮字符過濾問題)。
          1.6.2?#{}${}用法總結(jié) 1)#{}在使用時(shí),會(huì)根據(jù)傳遞進(jìn)來的值來選擇是否加上雙引號(hào),因此我們傳遞參數(shù)的時(shí)候一般都是直接傳遞,不用加雙引號(hào),${}則不會(huì),我們需要手動(dòng)加
          2)在傳遞一個(gè)參數(shù)時(shí),我們說了#{}中可以寫任意的值,${}則必須使用value;即:${value}
          3)#{}針對(duì)SQL注入進(jìn)行了字符過濾,${}則只是作為普通傳值,并沒有考慮到這些問題

          4)#{}的應(yīng)用場(chǎng)景是為給SQL語(yǔ)句的where字句傳遞條件值,${}的應(yīng)用場(chǎng)景是為了傳遞一些需要參與SQL語(yǔ)句語(yǔ)法生成的值。



          全棧架構(gòu)社區(qū)交流群

          ?「全棧架構(gòu)社區(qū)」建立了讀者架構(gòu)師交流群,大家可以添加小編微信進(jìn)行加群。歡迎有想法、樂于分享的朋友們一起交流學(xué)習(xí)。

          掃描添加好友邀你進(jìn)架構(gòu)師群,加我時(shí)注明姓名+公司+職位】

          看完本文有收獲?請(qǐng)轉(zhuǎn)發(fā)分享給更多人


          往期資源:


          Flutter 移動(dòng)應(yīng)用開發(fā)實(shí)戰(zhàn) 視頻(開發(fā)你自己的抖音APP)
          Java面試進(jìn)階訓(xùn)練營(yíng) 第2季(分布式篇)
          Java高級(jí) - 分布式系統(tǒng)開發(fā)技術(shù)視頻
          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色黄视频在线观看 | 超碰自拍在线 | 日韩精品一级 | 黄色电影网站在线观看中文字幕 | 日韩和欧美的一区二区 |