摘要:原因就是傳入的和原有的單引號,正好組成了,而后面恒等于,所以等于對這個庫執(zhí)行了查所有的操作。類比的執(zhí)行流程和原有的我們使用的方法就是??梢岳斫鉃榫褪怯脕斫馕龆ㄖ频姆柕恼Z句。后續(xù)的流程,就和正常的流程一致了。
前言
在JDBC中,主要使用的是兩種語句,一種是支持參數(shù)化和預編譯的PrepareStatement,能夠支持原生的Sql,也支持設置占位符的方式,參數(shù)化輸入的參數(shù),防止Sql注入,一種是支持原生Sql的Statement,有Sql注入的風險。
在使用Mybatis進行開發(fā)過程中,隱藏了底層具體使用哪一種語句的細節(jié),我們通過使用#和$告訴Mybatis,我們實際上進行的是怎么樣的操作,需要對語句進行參數(shù)化還是說直接保持原生狀態(tài)就好。
今天我們主要看一下使用兩種符號使用時系統(tǒng)應對Sql注入的表現(xiàn)和Mybatis在內(nèi)部是如何對他們處理的源碼分析。
和$在應對Sql注入上的區(qū)別表現(xiàn)利用現(xiàn)有應用程序,將(惡意的)SQL命令注入到后臺數(shù)據(jù)庫引擎執(zhí)行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網(wǎng)站上的數(shù)據(jù)庫,而不是按照設計者意圖去執(zhí)行SQL語句。
比如說根據(jù)學生姓名查學生信息,會傳入一個name的參數(shù),假設學生姓名是方方,那么Sql就是
SELECT id,name,age FROM student WHERE name = "方方";
在沒有做防Sql注入的時候,我們的Sql語句可能是這么寫的
正常情況下查出姓名符合方方的學生信息。
但如果我們對傳入的姓名參數(shù)做一些更改,比如改成anything" OR "x"="x,那么拼接而成的Sql就變成了
SELECT id,name,age FROM student WHERE name = "anything" OR "x"="x"
庫里面所有的學生信息都被拉了出來,是不是很可怕。原因就是傳入的anything" OR "x"="x和原有的單引號,正好組成了 "anything" OR "x"="x",而OR后面恒等于1,所以等于對這個庫執(zhí)行了查所有的操作。
防范Sql注入的話,就是要把整個anything" OR "x"="x中的單引號作為參數(shù)的一部分,而不是和Sql中的單引號進行拼接
使用了#即可在Mybatis中對參數(shù)進行轉(zhuǎn)義
我們看一下發(fā)送到數(shù)據(jù)庫端的Sql語句長什么樣子。
SELECT id,name,age FROM student WHERE name = "anything" OR "x"="x"
從上述代碼中我們可以看到參數(shù)中的所有單引號統(tǒng)統(tǒng)被轉(zhuǎn)移了,這都是JDBC中PrepareStatement的功勞,如果在數(shù)據(jù)庫服務端開啟了預編譯,則是服務端來做了這件事情。
具體可以看我之前寫的這篇: JDBC與Mysql的那些事,里面解釋了為何PrepareStatement能做到這件事情。
源碼在以前的文章中,我們說明過Mybatis的執(zhí)行流程主要部件,SqlSession 提供給用戶操作的Api,Executor 具體執(zhí)行對數(shù)據(jù)庫的操作,但其實在Executor內(nèi)部還會再委托給StatementHandler這個接口。
這個Handler的實現(xiàn)類就是代表了JDBC中的操作語句,CallableStatementHandler、PrepareStatementHandler和SimpleStatementHandler就會代表對JDBC中的CallableStatement,PrepareStatement和Statement,這些handler的內(nèi)部就會調(diào)用JDBC中的相關Statement。
類比Mybatis的執(zhí)行流程和JDBC原有的我們使用的方法就是。
Mybatis: Sqlsession -> Executor -> StatementHandler -> ResultHandler
JDBC: Connection -> Statement -> Result
因此我們可以知道對JDBC語句的操作都會在StatementHandler內(nèi)部。
在PrepareStatementHandler中會使用paramterize對Statement進行參數(shù)化,在其中他會委托給DefualtParameterHandler進行操作。我們通過兩種不同的語句,看一下,Debug下這段代碼的不同。
首先是使用$符號,它是會直接在Sql中進行拼接的,從下圖可知,在進行參數(shù)化的時候,Sql語句已經(jīng)被拼接完成了,見originSql。
進入DefualtParameterHandler內(nèi)部,如下圖可知,我們看到,這兒boundSql的ParameterMappings不存在,所以不用執(zhí)行第二個紅框處,設置對應占位符的操作。
然后,我們看一下當使用#的時候,同樣的代碼,會得到什么樣的處理結果。從下圖可知,當使用#的時候,原有的#{value}被替換成了?號,也就是我們熟知的JDBC中的占位符。
再進入DefualtParameterHandler的時候, 此時會有ParameterMappings,value -> anything" OR "x"="x",找到合適的TypeHandler塞入PrepareStatement中。
**從上文的分析中,我們得到的就是,當使用的時候,的時候,{value},是直接被替換為了對應的值,沒有參數(shù)映射,不會進行設置占位符的操作,當使用#的時候,#{}會被替換為?號,有參數(shù)映射,會在DefaultParameterHandler中進行設置占位符的操作。
問題
1 為什么默認使用的語句是PrepareStatementHandler
2 和#是什么時候被替換的,為什么對應的BoundSql,$時沒有映射,#有映射。
帶著這兩個問題我們來看一下,Mybatis的初始化階段,為節(jié)省篇幅,僅列出大致路徑,和關鍵代碼。
Mybatis是通過SqlSessionFactory build出來的,會解析映射文件,大致路徑就是
SqlSessionFactoryBuilder -> XmlConfigBuilder->XMLMapperBuilder->XMLStatementBuilder。
在XMLStatementBuilder的parseStatementNode負責了生成MappedStatement,首先回答第一個問題。當你不指定statementType時,Mybatis默認使用的就是PrepareStatementHandler,這里的StatementType,在后續(xù)流程中使用RoutingStatementHandler選擇使用哪一個StatementHandler。
然后繼續(xù)看第二個問題,$和#是怎么被替換的。
在之前我們提到了,BoundSql中包含了Sql主體,同時其中的參數(shù)映射決定了后續(xù)是否要進行參數(shù)化,在$和#時,表現(xiàn)是不同的。
BoudSql來自于MappedStatement,在MappedStatement中,獲取BoundSql的任務會委托給SqlSource接口。所以我們接下來主要看SqlSource是如何生成的。
XMLLandDriver可以理解為就是用來解析Mybatis定制的XML符號的語句。他會把具體解析符號的職責交給XMLScriptBuilder的parseScriptNode方法。
parseDynamicTags中會把語句用TextSql包裝起來,然后使用isDynamic方法,在方法中使用GerenericTokenParser判斷是否是動態(tài)語句。如果其中包含$,就是動態(tài)的,如果是#就不是動態(tài)的,使用的Handler是DrynamicCheckerTokenParser。
在進入parse方法后,主要看以下這一段。
這里會使用TokenHandler不同的實現(xiàn)類,對表達式進行進一步的處理,這里是對Sql自后的完善,在判斷isDynamic中,使用的是DrynamicCheckerTokenParser,一個最簡單的實現(xiàn)。
parse完成后,如果isDynamic是true的話,就是動態(tài)語句,使用DynamicSqlSource。
如果是非動態(tài)的話,其實一般就是指使用了#的語句,使用RawSqlSource,在其中,還會進一步解析。
從下圖中可以看到,這個TokenParser這回使用的是#{},而且使用的是ParameterMappingTokenHandler。
ParameterMappingTokenHandler的handlerToken方法中,完成了添加參數(shù)映射和替換#{value}為?的職責。
從以上我們可以知道,使用#在初始化階段,會被替換成?號,同時生成參數(shù)映射,而使用$在初始化階段,沒有什么特別的地方,僅僅做了一個是否動態(tài)語句的判斷。
在初始化完畢后,我們進入getBoundSql方法,看一下DynamicSqlSource和StaticSource在此刻做了什么,首先是DynamicSqlSource。
在其中,首先會生成一個DynamicContext,主要就是 生成bindings,一個是 "_parameter" -> "anything" OR "x"="x",一個是"_databaseId" -> "null"
然后使用了apply方法,我理解這里是要去做替換了。具體還是使用${}去判斷,和上文一致,只不過這里使用的是BindingTokenParser。
看一下BindingTokenParser的HandleToken方法。
上述代碼的效果,就是會使用Ognl,使用value在Bindings中,找對應的值,最后返回,拼接在Sql中,這也就是為什么會有Sql注入風險的原因。使用value是因為Ognl去找的時候,就會使用value這個默認值,所以需要在bindings額外加入這么一個鍵值對,有興趣可以繼續(xù)往下看ONGL相關的東西。
接下來是生成SqlSource,使用的是SqlSourceBuilder的parse方法。
在前文介紹過,在這個parse方法里,是用#{}來判斷的,所以走不到ParameterMappingTokenHandler的handlerToken方法,也就無法添加參數(shù)映射了,這個直接返回一個StaticSqlSource,這也解釋了為什么使用$時,參數(shù)映射為空。
再接下去就是獲取BoundSql,使用的是StaticSqlSource,直接根據(jù)參數(shù),實例化了一個,參數(shù)映射為空。
當使用#的時候,使用的就是StaticSqlSource,直接實例化,因為參數(shù)映射在之前初始化的階段,也生成好了,所以很簡單的一個流程。
后續(xù)的流程,就和Mybatis正常的流程一致了。
總結本文主要剖析了Mybatis中$和#兩種符號使用上的不同,以及使用這兩種符號時,源碼流程上的區(qū)別。建議大家都使用#號,在orm這層也規(guī)避到Sql注入的風險。
倘若您有疑問或者有進一步想了解內(nèi)容,歡迎留言給我。
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67332.html
摘要:無論是在預處理語句中設置一個參數(shù)時,還是從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成類型。這個抽象類實現(xiàn)了接口,這個接口主要定義了類型轉(zhuǎn)換的幾種操作。至于這個抽象類繼承的,主要是提供了獲取這個具體是哪個類型。 TypeHandlers 無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數(shù)時,還是從結果集中取出一個值時, 都會用...
摘要:北京解決辦法在字段的時候使用,下面是改動后的映射文件。北京那么我們來看看它是如何生效的,主要的代碼在哪里。源碼層面的話,依舊在的中處理返回集合。總結大致上,完成映射主要是兩種方式。使用預先定義好映射關系,也是最后根據(jù)和反射,完成字段的賦值。 前言 考慮到在Select時使用AS和方案一其實沒什么差別,在介紹ResultMap之前,順便帶過一下。 方案二-Select .... AS 當...
摘要:從使用到原理學習線程池關于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現(xiàn)在軟件開發(fā)中,分散于應用中多出的功能被稱為橫切關注點如事務安全緩存等。 Java 程序媛手把手教你設計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...
閱讀 2426·2021-09-22 16:01
閱讀 3188·2021-09-22 15:41
閱讀 1204·2021-08-30 09:48
閱讀 520·2019-08-30 15:52
閱讀 3350·2019-08-30 13:57
閱讀 1744·2019-08-30 13:55
閱讀 3701·2019-08-30 11:25
閱讀 791·2019-08-29 17:25