MyBaits中#{}如何防止注入

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

1.1 ${}和#{}演示

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

數(shù)據(jù)

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>

執(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<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();
}

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

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

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

1.2 SQL注入問(wèn)題

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

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

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

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

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

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


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

我們傳遞的參數(shù)是aaa' or 1=1 --咐熙,導(dǎo)致查詢出來(lái)了全部的數(shù)據(jù)。大家可以想象一下,如果我是要根據(jù)id刪除呢?

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

如果我傳遞的是:1' or 1=1; --蕾久,結(jié)果會(huì)是什么樣,我想大家應(yīng)該已經(jīng)知道了拌夏。如果上面使用的是#{}就不會(huì)出現(xiàn)SQL注入的問(wèn)題了

使用#{}
1.3 ${}和#{}的區(qū)別

#{}匹配的是一個(gè)占位符僧著,相當(dāng)于JDBC中的一個(gè)?履因,會(huì)對(duì)一些敏感的字符進(jìn)行過(guò)濾,編譯過(guò)后會(huì)對(duì)傳遞的值加上雙引號(hào)盹愚,因此可以防止SQL注入問(wèn)題栅迄。

${}匹配的是真實(shí)傳遞的值,傳遞過(guò)后皆怕,會(huì)與sql語(yǔ)句進(jìn)行字符串拼接毅舆。${}會(huì)與其他sql進(jìn)行字符串拼接,不能預(yù)防sql注入問(wèn)題愈腾。

查看#{}和${}生成的SQL語(yǔ)句

查看#{}和${}生成的SQL語(yǔ)句
String abc=“123”;

#{abc}="123"

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

我們翻開MySQL驅(qū)動(dòng)的源碼一看究竟;打開PreparedStatement類的setString()方法(MyBatis在#{}傳遞參數(shù)時(shí)虱黄,是借助setString()方法來(lái)完成悦即,${}則不是):

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) {  //遍歷字符串,獲取到每個(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)觀察

斷點(diǎn)

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

斷點(diǎn)

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

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

select * from user where username like 'aaa\' or 1=1 -- '
查詢結(jié)果

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

select * from user where username like 'aaa' or 1=1 -- '
根"/"去掉
1.5 #{}和${}的應(yīng)用場(chǎng)景

既然#{}${}好那么多,那為什么還要有${}這個(gè)東西存在呢泳叠?干脆都用#{}不就萬(wàn)事大吉嗎作瞄?

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

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

例如現(xiàn)在要進(jìn)行模糊查詢节预,查詢user表中姓張的所有員工的信息
sql語(yǔ)句為:select * from user where name like '張%'

1叶摄、此時(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 '張'"%"

2、如果傳入的參數(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 '張%'

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

1.5.2 什么情況下用${}?

場(chǎng)景舉例:


場(chǎng)景舉例

代碼測(cè)試:

代碼測(cè)試

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

執(zhí)行結(jié)果

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


異常

二拙泽、總結(jié)

MyBatis的#{}之所以能夠預(yù)防SQL注入是因?yàn)榈讓邮褂昧?code>PreparedStatement類的setString()方法來(lái)設(shè)置參數(shù)淌山,此方法會(huì)獲取傳遞進(jìn)來(lái)的參數(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注入的問(wèn)題(不會(huì)考慮字符過(guò)濾問(wèn)題)个粱。

#{}在使用時(shí),會(huì)根據(jù)傳遞進(jìn)來(lái)的值來(lái)選擇是否加上雙引號(hào)翻翩,因此我們傳遞參數(shù)的時(shí)候一般都是直接傳遞都许,不用加雙引號(hào),${}則不會(huì)体斩,我們需要手動(dòng)加

#{}針對(duì)SQL注入進(jìn)行了字符過(guò)濾梭稚,${}則只是作為普通傳值,并沒有考慮到這些問(wèn)題

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

三、項(xiàng)目中使用like

MyBaits中用#{}防止注入

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

    @ApiModelProperty(value = "抽獎(jiǎng)活動(dòng)ID")
    private String activityNo;

    @ApiModelProperty(value = "抽獎(jiǎng)活動(dòng)標(biāo)題")
    private String activityName;

    @ApiModelProperty(value = "參與人數(shù)(大于)")
    private Integer joinNumberStart;

    @ApiModelProperty(value = "參與人數(shù)(小于)")
    private Integer joinNumberEnd;

    @ApiModelProperty(value = "狀態(tài)")
    private PublishStatusEnum publishStatus;

    @ApiModelProperty(value = "活動(dòng)類型(1=抽獎(jiǎng)+兌獎(jiǎng)暇昂;2=僅抽獎(jiǎng);3=僅兌獎(jiǎng))")
    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>

原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末急波,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瘪校,更是在濱河造成了極大的恐慌澄暮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阱扬,死亡現(xiàn)場(chǎng)離奇詭異泣懊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)麻惶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門馍刮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人窃蹋,你說(shuō)我怎么就攤上這事卡啰。” “怎么了警没?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵匈辱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杀迹,道長(zhǎng)亡脸,這世上最難降的妖魔是什么盈魁? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任椰憋,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苹丸。我一直安慰自己贝次,他們只是感情好挚瘟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布哄褒。 她就那樣靜靜地躺著,像睡著了一般埂陆。 火紅的嫁衣襯著肌膚如雪苛白。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天焚虱,我揣著相機(jī)與錄音购裙,去河邊找鬼。 笑死鹃栽,一個(gè)胖子當(dāng)著我的面吹牛躏率,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播民鼓,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼薇芝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了丰嘉?” 一聲冷哼從身側(cè)響起夯到,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饮亏,沒想到半個(gè)月后耍贾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡路幸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年荐开,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劝赔。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡誓焦,死狀恐怖胆敞,靈堂內(nèi)的尸體忽然破棺而出着帽,到底是詐尸還是另有隱情,我是刑警寧澤移层,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布仍翰,位于F島的核電站,受9級(jí)特大地震影響观话,放射性物質(zhì)發(fā)生泄漏予借。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灵迫。 院中可真熱鬧秦叛,春花似錦、人聲如沸瀑粥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狞换。三九已至避咆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間修噪,已是汗流浹背查库。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黄琼,地道東北人樊销。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像脏款,于是被迫代替她去往敵國(guó)和親现柠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容