實例講解MyBatis如何防止SQL注入
?SQL注入是一種很簡單的攻擊手段,但直到今天仍然十分常見。究其原因不外乎:No patch for stupid。為什么這么說,下面就以JAVA為例進(jìn)行說明:
假設(shè)數(shù)據(jù)庫中存在這樣的表:
table user( id varchar(20) PRIMARY KEY , name varchar(20) , age varchar(20) );
然后使用JDBC操作表:
private String getNameByUserId(String userId) {
Connection conn = getConn();//獲得連接
String sql = "select name from user where id=" + userId;
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs=pstmt.executeUpdate();
......
}
上面的代碼經(jīng)常被一些開發(fā)人員使用。想象這樣的情況,當(dāng)傳入的userId參數(shù)為"3;drop table user;"時,執(zhí)行的sql語句如下:
select name from user where id=3; drop table user;
數(shù)據(jù)庫在編譯執(zhí)行之后,刪除了user表。瞧,一個簡單的SQL注入攻擊生效了!之所以這樣,是因為上面的代碼沒有符合編程規(guī)范。
當(dāng)我們按照規(guī)范編程時,SQL注入就不存在了。這也是避免SQL注入的第一種方式:預(yù)編譯語句,代碼如下:
Connection conn = getConn();//獲得連接
String sql = "select name from user where id= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
ResultSet rs=pstmt.executeUpdate();
......
為什么上面的代碼就不存在SQL注入了呢?因為使用了預(yù)編譯語句,預(yù)編譯語句在執(zhí)行時會把"select name from user where id= ?"語句事先編譯好,這樣當(dāng)執(zhí)行時僅僅需要用傳入的參數(shù)替換掉?占位符即可。而對于第一種不符合規(guī)范的情況,程序會先生成sql語句,然后帶著用戶傳入的內(nèi)容去編譯,這恰恰是問題所在。
除了使用預(yù)編譯語句之外,還有第二種避免SQL注入攻擊的方式:存儲過程。存儲過程(Stored Procedure)是一組完成特定功能的SQL語句集,經(jīng)編譯后存儲在數(shù)據(jù)庫中,用戶通過調(diào)用存儲過程并給定參數(shù)(如果該存儲過程帶有參數(shù))就可以執(zhí)行它,也可以避免SQL注入攻擊
Connection conn = getConn();
stmt = conn.prepareCall("{call name_from_user(?,?)}");
stmt.setInt(1,2);
stmt.registerOutParameter(2, Types.VARCHAR);
stmt.execute();
String name= stmt.getString(2);
上面的代碼中對應(yīng)的存儲過程如下:
use user; delimiter // create procedure name_from_user(in user_id int,out user_name varchar(20)) begin select name into user_name from user where id=user_id; end // delimiter ;
當(dāng)然用戶也可以在前端做字符檢查,這也是一種避免SQL注入的方式:比如對于上面的userId參數(shù),用戶檢查到包含分號就提示錯誤。
不過,從最根本的原因看,SQL注入攻擊之所以存在,是因為app在訪問數(shù)據(jù)庫時沒有使用最小權(quán)限。想來也是,大家好像一直都在使用root賬號訪問數(shù)據(jù)庫。
那么mybatis是如何避免sql注入攻擊的呢?還是以上面的表user為例:
假設(shè)mapper文件為:
<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = #{userId}
</select>
對應(yīng)的java文件為:
public interface UserMapper{
String getNameByUserId(@Param("userId") String userId);
}
可以看到輸入的參數(shù)是String類型的userId,當(dāng)我們傳入userId="34;drop table user;"后,打印的語句是這樣的:
select name from user where id = ?
不管輸入何種userID,他的sql語句都是這樣的。這就得益于mybatis在底層實現(xiàn)時使用預(yù)編譯語句。數(shù)據(jù)庫在執(zhí)行該語句時,直接使用預(yù)編譯的語句,然后用傳入的userId替換占位符?就去運行了。不存在先替換占位符?再進(jìn)行編譯的過程,因此SQL注入也就沒有了生存的余地了。
那么mybatis是如何做到sql預(yù)編譯的呢?其實框架底層使用的正是PreparedStatement類。PreparedStaement類不但能夠避免SQL注入,因為已經(jīng)預(yù)編譯,當(dāng)N次執(zhí)行同一條sql語句時,節(jié)約了(N-1)次的編譯時間,從而能夠提高效率。
如果將上面的語句改成:
<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = ${userId}
</select>
當(dāng)我們輸入userId="34;drop table user;"后,打印的語句是這樣的:
select name from user where id = 34;drop table user;
此時,mybatis沒有使用預(yù)編譯語句,它會先進(jìn)行字符串拼接再執(zhí)行編譯,這個過程正是SQL注入生效的過程。
因此在編寫mybatis的映射語句時,盡量采用“#{xxx}”這樣的格式。若不得不使用“${xxx}”這樣的參數(shù),要手工地做好過濾工作,來防止sql注入攻擊。
以上所述是小編給大家介紹的MyBatis如何防止SQL注入,希望對大家有所幫助。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
教你利用JAVA實現(xiàn)可以自行關(guān)閉服務(wù)器的方法
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著利用JAVA實現(xiàn)可以自行關(guān)閉服務(wù)器的方法展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Java實現(xiàn)NIO聊天室的示例代碼(群聊+私聊)
這篇文章主要介紹了Java實現(xiàn)NIO聊天室的示例代碼(群聊+私聊),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
詳解Spring boot使用Redis集群替換mybatis二級緩存
本篇文章主要介紹了詳解Spring boot使用Redis集群替換mybatis二級緩存,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
SpringBoot Event 事件如何實現(xiàn)異步延遲執(zhí)行
這篇文章主要介紹了Spring Boot Event 事件如何實現(xiàn)異步延遲執(zhí)行問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02
Java之a(chǎn)pi網(wǎng)關(guān)斷言及過濾器案例講解
這篇文章主要介紹了Java之a(chǎn)pi網(wǎng)關(guān)斷言及過濾器案例講解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08

