Mybatis常用注解中的SQL注入實例詳解
前言
MyBatis3提供了新的基于注解的配置。主要在MapperAnnotationBuilder中,定義了相關(guān)的注解:
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
...
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
......
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}增刪改查占據(jù)了絕大部分的業(yè)務(wù)操作,通過注解不在需要配置繁雜的xml文件,越來越多的sql交互均通過注解來實現(xiàn)。從MapperAnnotationBuilder可以看到Mybatis提供了以下相關(guān)的注解:
- @Select
- @Insert
- @Update
- @Delete
- @SelectProvider
- @InsertProvider
- @UpdateProvider
- @DeleteProvider
例如如下例子,使用@Select注解直接編寫SQL完成數(shù)據(jù)查詢:
@Mapper
public interface UserMapper {
@Select("select * from t_user")
List<User> list();
}使用類似@SelectProvider高級注解可以指定某個工具類的方法來動態(tài)編寫SQL,以應(yīng)對復(fù)雜的業(yè)務(wù)需求。
以@SelectProvider 為例,查看具體的實現(xiàn),主要包含兩個注解屬性,其中type表示工具類,method 表示工具類的某個方法,用于返回具體的SQL:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InsertProvider {
// 用于指定獲取 sql 語句的指定類
Class<?> type();
// 指定類中要執(zhí)行獲取 sql 語句的方法
String method();
}使用方法如下,在ProjectSql類的getContentByProjectIds方法定義相關(guān)的sql即可,sql的定義可以通過org.apache.ibatis.jdbc.SQL來快速實現(xiàn):
@SelectProvider(type = ProjectSql.class, method = "getContentByProjectIds")
List<Integer> getContentByProjectIds(List<Integer> projectIds);
常見注入場景
2.1普通注解
實際上跟xml配置中對應(yīng)的標(biāo)簽語法是一樣的(例如@Select對應(yīng)<select>標(biāo)簽),所以注入場景也是類似的。
在Mybatis中,#的作用主要是替換預(yù)編譯語句(PrepareStatement)中的占位符?,$是直接的SQL拼接。以like模糊查詢 為例子:
例如如下例子:
跟xml配置一樣,like模糊查詢直接使用#預(yù)編譯的方式進(jìn)行注解的話會觸發(fā)異常,所以很多時候直接使用$進(jìn)行注解:
@Select("SELECT id, name, age, email FROM user where name like '${name}'")List<User> queryUserByName(@Param("name") String name);那么此時name前端用戶可控的話,將導(dǎo)致SQL注入風(fēng)險。
圖片查看sql日志,成功執(zhí)行1/0觸發(fā)sql error,說明注入成功:
處理這類SQL問題也很簡單,使用sql的內(nèi)置函數(shù)進(jìn)行拼接,拼接后再采用#預(yù)編譯的方式進(jìn)行查詢。例如上面案例是h2數(shù)據(jù)庫的,使用'||'拼接再進(jìn)行預(yù)編譯處理即可:
@Select("SELECT id, name, age, email FROM user where name like '%'||#{name}||'%'")List<User> queryUserByName(@Param("name") String name);此時已使用預(yù)編譯進(jìn)行SQL查詢:
此外,類似Order by、動態(tài)表名,無法采用預(yù)編譯的方式情況,可以在在代碼層使用間接引用的方式進(jìn)行處理。
對于范圍查詢in,熟悉mybatis注入的話,是需要使用MyBatis自帶的循環(huán)指令foreach來解決SQL語句動態(tài)拼接的,當(dāng)使用注解時,就需要使用< script>標(biāo)簽來引入foreach了。
2.2 動態(tài)sql
2.2.1 使用< script>
要在帶普通注解的映射器接口類中使用動態(tài) SQL,可以使用script 元素。跟xml類似,主要是如下的元素:
if choose (when, otherwise) trim (where, set) foreach
相關(guān)的注入場景跟2.1也是類似的。也是離不開$。此外,在進(jìn)行同條件多值查詢(例如范圍查詢in)的時候,可以使用MyBatis自帶的循環(huán)指令foreach來解決SQL語句動態(tài)拼接的問題。
2.2.2 使用Provider注解
可以通過使用Provider注解指定某個工具類的方法來動態(tài)編寫SQL。以@SelectProvider為例:
首先在mapper中使用@SelectProvider定義相關(guān)的方法,其中type表示工具類,method 表示工具類的某個方法,用于返回具體的SQL。例如下面的例子:
通過傳遞userIds以及name,查詢相關(guān)的用戶信息,在UserInfoSql類的getUserInfoByids方法定義了具體的SQL內(nèi)容:
/**
* @param userIds 必填
* @param name 可選
* @return
*/
@SelectProvider(type = UserInfoSql.class, method = "getUserInfoByids")
List<User> getUserInfoByids(List<Long> userIds, String name);
class UserInfoSql {
public String getUserInfoByids(List<Long> userIds, String name) {
SQL sql = new SQL();
sql.SELECT("id, name, age, email");
sql.FROM("user");
sql.WHERE("id in(" + Joiner.on(',').join(userIds) + ")");
if(StringUtil.isNotBlank(name)){
sql.WHERE("name like '%" + name + "%'");
}
sql.ORDER_BY("id desc");
return sql.toString();
}
}在Controller調(diào)用具體方法就可以進(jìn)行sql查詢了:
@RequestMapping(value = "/getUserInfoByids")
public List<User> getUserInfoByids(String name,@RequestParam List<Long> userIds){
List<User> userList = userMapper.getUserInfoByids(userIds,name);
return userList;
}正常請求返回對應(yīng)的用戶信息:
前面是通過MyBatis 3 提供的工具類org.apache.ibatis.jdbc.SQL來生成SQL的。該類提供了類似select、where、ORDER_BY等方法來完成SQL生成的操作。這里有個誤區(qū),很多開發(fā)認(rèn)為這里工具類會進(jìn)行相關(guān)的預(yù)編譯處理。
實際上Provider其實只需要返回一個SQL字符串,工具類只不過用了一些關(guān)鍵字做格式化而已,甚至可以直接使用StringBuffer拼接SQL語句。同樣是上面的例子,List userIds是long類型,但是name是String類型,可以嘗試注入:
查看相關(guān)日志,成功執(zhí)行1/0邏輯觸發(fā)SQL error,也印證了Provider實際上只是 SQL 拼接,沒有做相關(guān)的安全處理 :
相比@Select@,SelectProvider 只是在定義注解的方式上有所不同, 前者是直接定義 sql, 一個是在外部定義好 sql 直接引用, 沒本質(zhì)上的區(qū)別,所以解決方法是在對應(yīng)的sql場景,使用#進(jìn)行預(yù)編譯進(jìn)行處理,例如這里的like模糊查詢和in范圍查詢:
@SelectProvider(type = UserInfoSql.class, method = "getUserInfoByids")
List<User> getUserInfoByids(@Param("userIds")List<Long> userIds,@Param("name")String name);
class UserInfoSql {
public String getUserInfoByids(@Param("userIds")List<Long> userIds, @Param("name")String name) {
StringBuilder sql = new StringBuilder(128);
sql.append("< script>SELECT id, name, age, email FROM user WHERE (id in");
sql.append("<foreach item='item' collection='userIds' open='(' separator=',' close=')'>#{item}</foreach>");
if(StringUtil.isNotBlank(name)){
sql.append("and name like '%'||#{name}||'%')");
}
sql.append("ORDER BY id desc</script>");
return sql.toString();
}
}查看sql日志,此時使用預(yù)編譯進(jìn)行sql處理,避免了SQL注入風(fēng)險。
總結(jié)
到此這篇關(guān)于Mybatis常用注解中的SQL注入的文章就介紹到這了,更多相關(guān)Mybatis注解的SQL注入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Spring Boot最新版優(yōu)雅停機(jī)的方法
這篇文章主要介紹了Spring Boot最新版優(yōu)雅停機(jī)的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
Java替換字符串replace和replaceAll方法舉例詳解
這篇文章主要介紹了Java中替換字符串的幾種方法,包括String類的replace()、replaceAll()、replaceFirst()方法,以及StringBuilder和StringBuffer類的replace()方法,還提到了一些第三方庫,如Hutool,它們提供了更豐富的字符串處理功能,需要的朋友可以參考下2025-02-02
Spring Data JPA 關(guān)鍵字Exists的用法說明
這篇文章主要介紹了Spring Data JPA 關(guān)鍵字Exists的用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Feign實現(xiàn)多文件上傳,Open?Feign多文件上傳問題及解決
這篇文章主要介紹了Feign實現(xiàn)多文件上傳,Open?Feign多文件上傳問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
SpringBoot+SpringCloud用戶信息微服務(wù)傳遞實現(xiàn)解析
這篇文章主要介紹了SpringBoot+SpringCloud實現(xiàn)登錄用戶信息在微服務(wù)之間的傳遞,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11

