Mybatis
SpringBoot整合Mybatis
配置文件方式
配置文件
spring.application.name=springboot-testspring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#classpath对应resources目录mybatis.config-location=classpath:mybatis/mybatis-config.xmlmybatis.mapper-locations=classpath:mybatis/mapper/*.xml#实体类位置mybatis.type-aliases-package=com.example.springboottest.VO
添加相关pom依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-test</name> <description>springboot-test</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.1</version> <!-- 请检查最新版本 --> </dependency> <!-- MySQL Driver (如果你用 MySQL) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
编写mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <typeAliases> <typeAlias alias="Integer" type="java.lang.Integer" /> <typeAlias alias="Long" type="java.lang.Long" /> <typeAlias alias="HashMap" type="java.util.HashMap" /> <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /> <typeAlias alias="ArrayList" type="java.util.ArrayList" /> <typeAlias alias="LinkedList" type="java.util.LinkedList" /> </typeAliases></configuration>
MVC三层架构
编写mapper接口
package com.example.springboottest.mapper;import com.example.springboottest.Vo.TestMybatisVo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper #@Mapper注解标记让Mybatis把该接口注入到spring的上下文中public interface TestMapper { List<TestMybatisVo> aaa();}
对应mapper文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.example.springboottest.mapper.TestMapper" > <!-- 对应数据库中相应字段 <resultMap id="BaseResultMap" type="com.neo.entity.UserEntity" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="userName" property="userName" jdbcType="VARCHAR" /> <result column="passWord" property="passWord" jdbcType="VARCHAR" /> <result column="user_sex" property="userSex" javaType="com.neo.enums.UserSexEnum"/> <result column="nick_name" property="nickName" jdbcType="VARCHAR" /> </resultMap> --> <!-- 定义可以复用重复的片段 <sql id="Base_Column_List" > id, userName, passWord, user_sex, nick_name </sql> --> <!-- id对应mapper接口的方法函数 resultType对应查询结果的封装类型--> <select id="aaa" resultType="TestMybatisVo" > SELECT * FROM people </select></mapper>
编写实体类,把查询出的数据进行封装
package com.example.springboottest.Vo;import lombok.Data;@Data #Lombok库动态生成get、set方法public class TestMybatisVo { private int id; private String name; private int age;}
编写service接口
package com.example.springboottest.service;import com.example.springboottest.Vo.TestMybatisVo;import java.util.List;public interface TestService { List<TestMybatisVo> test();}
编写serviceimpl实现类
package com.example.springboottest.service.impl;import com.example.springboottest.Vo.TestMybatisVo;import com.example.springboottest.mapper.TestMapper;import com.example.springboottest.service.TestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Servicepublic class TestServiceImpl implements TestService { private final TestMapper testMapper; @Autowired public TestServiceImpl(TestMapper testMapper) { this.testMapper = testMapper; } @Override public List<TestMybatisVo> test() { return testMapper.aaa(); }}
编写控制器
package com.example.springboottest.controller;import com.example.springboottest.Vo.TestMybatisVo;import com.example.springboottest.service.TestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController@RequestMapping("/test")public class TestController { private final TestService TestService; @Autowired public TestController(TestService TestService) { this.TestService = TestService; } @GetMapping public List<TestMybatisVo> aaa(){ System.out.println("test"); return TestService.test(); }}
注解方式
配置文件
spring.application.name=springboot-testspring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
添加相关pom依赖
同配置文件方式
MVC三层架构
编写Mapper接口
package com.example.springboottest.mapper;import com.example.springboottest.Vo.TestMybatisVo;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper #@Mapper注解标记让Mybatis把该接口注入到spring的上下文中public interface TestMapper { @Select("SELECT * FROM people") List<TestMybatisVo> aaa();}
编写实体类,把查询出的数据进行封装
package com.example.springboottest.Vo;import lombok.Data;@Data #Lombok库动态生成get、set方法public class TestMybatisVo { private int id; private String name; private int age;}
编写service接口
package com.example.springboottest.service;import com.example.springboottest.Vo.TestMybatisVo;import java.util.List;public interface TestService { List<TestMybatisVo> test();}
编写serviceimpl实现类
package com.example.springboottest.service.impl;import com.example.springboottest.Vo.TestMybatisVo;import com.example.springboottest.mapper.TestMapper;import com.example.springboottest.service.TestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Servicepublic class TestServiceImpl implements TestService { private final TestMapper testMapper; @Autowired public TestServiceImpl(TestMapper testMapper) { this.testMapper = testMapper; } @Override public List<TestMybatisVo> test() { return testMapper.aaa(); }}
编写控制器
package com.example.springboottest.controller;import com.example.springboottest.Vo.TestMybatisVo;import com.example.springboottest.service.TestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController@RequestMapping("/test")public class TestController { private final TestService TestService; @Autowired public TestController(TestService TestService) { this.TestService = TestService; } @GetMapping public List<TestMybatisVo> aaa(){ System.out.println("test"); return TestService.test(); }}
SQL注入常出现位置
${}传参方式
#{}符号接收参数会对参数和语句分离,从而有效防止sql注入,而当Mybatis采用${}包裹参数时可能会存在sql注入。
模糊查询
当使用like模糊查询时,使用#{}这个预编译形式程序会报错
新手程序员可能会直接使用${}
正确写法
order by排序
一条带order by的sql语句为
SELECT * FROM people ORDER BY name DESC ;
此处容易产生sql注入的地方为name字段处(根据name字段进行排序)
还有DESC(倒叙)
当name采用预编译时,sql语句不会报错,但是根据字段排序的效果不会生效
对比两次排序结果,发现传入sort这个字段并未生效(不会随传入值改变而改变)。
当DESC处采用预编译时,这是传入的DESC就成为了字符串,不能被解析成SQL的关键字导致报错
经验不足的程序员这两个点可能会采用${}进行包裹。
正确写法应该在java代码层面单独对该两处进行过滤处理。
in关键字
当语句使用in关键字,采用预编译会让语句失效
不采用预编译
但是存在sql注入风险
采用预编译语句会失效
经验不足的程序员此处可能会采用${}直接进行包裹。
正确写法应该用foreach进行循环
<select id="aaa" resultType="HashMap" > SELECT * FROM people WHERE id in <foreach collection="id" item="item" index="index" open="(" separator="," close=")"> #{item} </foreach> </select>
表拼接
当sql语句要动态拼接表名时,此时使用预编译也会产生报错
当不使用预编译
但是会产生sql注入
正确做法应该在java代码层面对id输入做限制
Mybatis-Plus
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
SpringBoot整合Mybatis-plus
配置文件
spring.application.name=springboot-mybatisplus-testspring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.cj.jdbc.Drivermybatis-plus:configuration:#mybatis-plus配置控制台打印完整带参数SQL语句mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true
添加相关pom依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot-mybatisplus-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-mybatisplus-test</name> <description>springboot-mybatisplus-test</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.10.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
MVC三层架构
准备实体类
package com.example.springbootmybatisplustest.Vo;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;@Data@TableName(value = "people")public class People { @TableId(value = "id", type = IdType.AUTO) private Integer id; private String name; private String age;}
创建mapper
package com.example.springbootmybatisplustest.Mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.springbootmybatisplustest.Vo.People;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface PeopleMapper extends BaseMapper<People> {}
创建service
package com.example.springbootmybatisplustest.Service;import com.baomidou.mybatisplus.extension.service.IService;import com.example.springbootmybatisplustest.Vo.People;public interface PeopleService extends IService<People> {}
创建serviceimpl(操作sql的语句会在impl处实现)
package com.example.springbootmybatisplustest.Service.Impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.springbootmybatisplustest.Mapper.PeopleMapper;import com.example.springbootmybatisplustest.Service.PeopleService;import com.example.springbootmybatisplustest.Vo.People;import org.springframework.stereotype.Service;@Servicepublic class PeopleServiceImpl extends ServiceImpl<PeopleMapper, People> implements PeopleService {}
创建控制器
package com.example.springbootmybatisplustest.Controller;import com.example.springbootmybatisplustest.Service.PeopleService;import com.example.springbootmybatisplustest.Vo.People;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")public class PeopleController { @Autowired private PeopleService peopleService; @GetMapping public People aaa(@RequestParam("id") int id){ System.out.println("test"); return peopleService.getById(id); }}
Mybatis-plus的持久层使用
我们的service继承了IService,mapper继承了BaseMapper
IService 是 MyBatis-Plus 提供的一个通用 Service 层接口,它封装了常见的 CRUD 操作,包括插入、删除、查询和分页等。通过继承 IService 接口,可以快速实现对数据库的基本操作,同时保持代码的简洁性和可维护性。
BaseMapper 是 Mybatis-Plus 提供的一个通用 Mapper 接口,它封装了一系列常用的数据库操作方法,包括增、删、改、查等。通过继承 BaseMapper,开发者可以快速地对数据库进行操作,而无需编写繁琐的 SQL 语句。
具体封装函数
https://baomidou.com/guides/data-interface/#_top
条件构造器
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
Mybatis-Plus提供Wrapper类去构造查询
-
• AbstractWrapper:这是一个抽象基类,提供了所有 Wrapper 类共有的方法和属性。它定义了条件构造的基本逻辑,包括字段(column)、值(value)、操作符(condition)等。所有的 QueryWrapper、UpdateWrapper、LambdaQueryWrapper 和 LambdaUpdateWrapper 都继承自 AbstractWrapper。 -
• QueryWrapper:专门用于构造查询条件,支持基本的等于、不等于、大于、小于等各种常见操作。它允许你以链式调用的方式添加多个查询条件,并且可以组合使用 and
和or
逻辑。 -
• UpdateWrapper:用于构造更新条件,可以在更新数据时指定条件。与 QueryWrapper 类似,它也支持链式调用和逻辑组合。使用 UpdateWrapper 可以在不创建实体对象的情况下,直接设置更新字段和条件。 -
• LambdaQueryWrapper:这是一个基于 Lambda 表达式的查询条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名。这种方式提高了代码的可读性和可维护性,尤其是在字段名可能发生变化的情况下。 -
• LambdaUpdateWrapper:类似于 LambdaQueryWrapper,LambdaUpdateWrapper 是基于 Lambda 表达式的更新条件构造器。它允许你使用 Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题。
具体使用
https://baomidou.com/guides/wrapper/#_top
SQL注入常出现位置
Mybatis-plus封装了很多函数去操作sql语句,正确调用MyBatis-Plus封装的操作sql的函数可以很大程度避免sql注入问题,但是在一些分页处的order by和动态表名的地方需要程序员单独另外处理的地方很可能产生sql注入漏洞。
分页场景下的sql注入
PaginationInnerInterceptor
PaginationInnerInterceptor 是 MyBatis-Plus 提供的一个分页插件,用于处理分页查询的拦截器
PaginationInnerInterceptor 主要负责在 SQL 查询中自动添加分页条件,确保查询只返回特定的页码数据。它通过拦截 SQL 执行,自动为 SQL 语句添加分页参数(例如 LIMIT 和 OFFSET),从而实现分页效果。
分页配置
添加pom依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.5.9</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.9</version></dependency>
创建分页拦截器
package com.example.springbootmybatisplustest.Config;import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加 // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType return interceptor; }}
编写控制器
package com.example.springbootmybatisplustest.Controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.core.metadata.IPage;import com.baomidou.mybatisplus.core.metadata.OrderItem;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.example.springbootmybatisplustest.Service.PeopleService;import com.example.springbootmybatisplustest.Vo.People;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController@RequestMapping("/page")public class TestController { @Autowired private PeopleService peopleService; @GetMapping public List<People> test(@RequestParam("page") int page, @RequestParam("size") int size){ //创建 QueryWrapper构建查询条件 QueryWrapper<People> qw = new QueryWrapper<>(); //创建 Page 对象,进行分页 Page<People> personPage = new Page<>(page, size); // 调用 page 方法进行分页查询 IPage<People> iPage = peopleService.page(personPage, qw); //取出数据进行封装 List<People> persons = iPage.getRecords(); return persons; }}
而当PaginationInnerInterceptor使用了addOrder根据魔某个字段排序且未做任何其他过滤处理就存在sql注入问题。
可以看到IF语句被成功带入到SQL语句中,同理同意存在如下问题的函数还有
page.setOrders();
在一些老版本中,以下函数也会存在问题,新版本已废弃如下函数
page.setAsc();page.setDesc();page.setAscs();page.setDescs();
pagehelper
PageHelper 是一个开源的 MyBatis 分页插件,它提供了简单易用的 API,可以方便地进行分页查询操作。使用 PageHelper 可以减少开发人员编写复杂的分页查询 SQL 语句的工作量,同时还支持多种数据库和多种分页方式。PageHelper 还提供了排序和筛选的功能,并且支持插件扩展,可以根据需求进行自定义。总的来说,PageHelper 是一个强大且方便的工具,可以大大简化在 MyBatis 中进行分页查询的操作。
其原理和PaginationInnerInterceptor一样,都是在order by中不能预编译产生的sql注入
在startPage传入排序参数
跟进调用setOrderBy
这时程序员在未做任何过滤的情况下就会产生sql注入问题。
条件构造器易产生sql注入的函数
拼接类
apply
apply 方法是 MyBatis-Plus 中用于构建查询条件的高级方法之一,它允许你直接拼接 SQL 片段到查询条件中,而直接拼接就会存在sql注入的安全风险。
正常查询
sql注入查询
last
last 方法是 MyBatis-Plus 中用于构建查询条件的高级方法之一,它允许你直接在查询的最后添加一个 SQL 片段,而不受 MyBatis-Plus 的查询优化规则影响,此处类似直接拼接,参数可控的话同样存在sql注入问题。
正常查询
sql注入查询
成功影响sql执行
其他拼接
existsnotExists
Order by类
见名知意,sql注入常出现的位置,排序处
常见函数如下
orderBy()orderByAsc()orderByDesc()
insql类
insqlnotinsql
本质是sql注入预编译中的in函数问题
groupBy和having
having 方法是 MyBatis-Plus 中用于构建查询条件的高级方法之一,它用于设置 HAVING 子句,通常与 GROUP BY 一起使用,用于对分组后的数据进行条件筛选。
groupBy参数sql的原因本质是一般groupBy后面跟的参数都会被当作关键字使用,而使用预编译会让sql语句进行报错。having后面跟的内容假如没做任何过滤就相当于直接拼接进了sql语句,进而产生sql注入问题。
点击下方小卡片或扫描下方二维码观看更多技术文章
师傅们点赞、转发、在看就是最大的支持
原文始发于微信公众号(猪猪谈安全):Mybatis&Mybatis-Plus sql注入总结
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论