MyBatis和SQL注入

MyBatis和SQL注入

什么是SQL注入?

在正常的sql里拼上“drop或delete”等危险sql,导致执行正常sql的时候,对数据库数据造成危害。

如何防止SQL注入?

1、尽量避免使用常见的数据库名和数据库结构。SQL注入需要攻击者本身对于数据库的结构有足够的了解才能成功,必须写对sql语句才生效。

2、使用正则表达式等字符串过滤手段限制数据项的格式、字符数目。

MyBatis中的SQL注入

MyBatis传参如果用${},相当于直接将参数拼接到sql里。

select * from student where name = ${name}; 

等价于:

select * from student where name = "小明"; 

MyBatis传参如果用 #{},上述sql等价于:

select * from student where name = ?; 
String sql = "SELECT * FROM student WHERE name=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"小明");

#{}方式传参能够很大程度防止sql注入。

Statement 和 PreparedStatement区别

关系:PreparedStatement继承自Statement,都是接口
区别:PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高

${}传参sql注入实例1

mapper源文件:

@Select({"select * from employees where emp_no = ${empNo}"})
List<Employees> sqlInjection1(String empNo);

调用方法:

EmployeesMapper employeesMapper = sqlSession.getMapper(EmployeesMapper.class);
employeesMapper.sqlInjection1("'500000' or 1 = 1");

执行结果:

16:56:26.148 [main] DEBUG c.s.m.EmployeesMapper.sqlInjection1 - ==>  Preparing: select * from employees where emp_no = '500000' or 1 = 1 
16:56:26.217 [main] DEBUG c.s.m.EmployeesMapper.sqlInjection1 - ==> Parameters: 
16:56:28.250 [main] DEBUG c.s.m.EmployeesMapper.sqlInjection1 - <==      Total: 300024

可以看到把所有的300024条记录都查出来了。如果将$改成#之后:

mapper源文件:

@Select({"select * from employees where emp_no = #{empNo}"})
List<Employees> sqlInjection1(String empNo);

执行结果是查询出来0条记录。

16:53:26.456 [main] DEBUG c.s.m.EmployeesMapper.sqlInjection1 - ==>  Preparing: select * from employees where emp_no = ? 
16:53:26.490 [main] DEBUG c.s.m.EmployeesMapper.sqlInjection1 - ==> Parameters: '500000' or 1 = 1(String)
16:53:26.551 [main] DEBUG c.s.m.EmployeesMapper.sqlInjection1 - <==      Total: 0

源码分析

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

1、STATEMENT:直接操作sql,不进行预编译
2、PREPARED:预处理,默认值
3、CALLABLE:执行存储过程

public enum StatementType {
  STATEMENT, PREPARED, CALLABLE
}

其中PreparedStatementHandler中的方法都强制将Statement转为PreparedStatement:

public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;}

  @Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;}

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;}

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;}

RoutingStatementHandler里根据StatementType来判断初始化哪一个StatementHandler接口的实现类。

在这里插入图片描述

那么StatementType从哪里来?

Configuration对象

​ mappedStatements属性

​ MappedStatement属性

​ StatementType属性

public class Configuration {
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

mappedStatements的key是sql的id,value是MappedStatement。如下图:

在这里插入图片描述

StatementType属性的值是在下面的地方初始化的:

public final class MappedStatement {
  private StatementType statementType;
    
    public static class Builder {
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;}.这里用到了建造者模式。
  }

通过在mapper的xml文件里设置:

<update id="updateByEmpNo" statementType="STATEMENT">
<update id="updateByEmpNo" statementType="PREPARED">

可以指定statementType值,进而决定StatementHandler接口的实现类。

如果statementType使用STATEMENT,那么传参就必须用$,否则会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值