mybatis入门实战&源码解读番外篇(1):mybatis中#{}和${}的区别以及SQL注入问题分析

#{}和${}的区别

  • #{}是预编译处理,${}是字符串替换;
  • mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来进行赋值;
  • mybatis在处理时,就是把{}替换成变量的值;
  • 使用#{}可以有效的防止SQL注入,提高系统的安全性。

SQL注入问题分析

#{}分析

首先来看一个mapper.xml文件:

<?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.mybatis.dao.IUserDao">
    <cache></cache>
   <select id="findUserByName" resultType="user" useCache="true" parameterType="java.lang.String">
        select * from user where name like #{name}
    </select>

上述语句在执行的时候,#{id}会被传入的内容替换,替换的时候将传入的内容加上引号当成字符串,例如传入的name为 ’%刘%‘,那么SQL语句在数据库中会这么执行:

select * from user where name like '%刘%';

从log4j可以看到执行过程如下:

2021-01-13 09:06:39,174 486    [           main] DEBUG is.dao.IUserDao.findUserByName  - ==>  Preparing: select * from user where name like ?
2021-01-13 09:06:39,203 515    [           main] DEBUG is.dao.IUserDao.findUserByName  - ==> Parameters: %刘%(String)
2021-01-13 09:06:39,223 535    [           main] DEBUG is.dao.IUserDao.findUserByName  - <==      Total: 1
User{id=3, name='刘德华', salary=36.65}

从日志可以看到实际上相当于执行了这行代码:

String sql ="select * from user where name like ?";
preparedStatement=connection.preparedStatement(sql);
preparedStatement.setString(1,"%刘%")

调用PreparedStatement的setString方法传入参数可以防止sql注入,安全性更高。PreparedStatement是java.sql包下面的一个接口,用来执行sql语句查询,通过调用connection.preparedStatement(sql)方法可以得到PreparedStatement对象,数据库会对sql语句进行预编译处理,预处理语句将会被预先编译好,这条预编译的sql查询语句能在将来的查询中重用。 说一句人话就是:在使用参数化查询的情况下,数据库系统不会将参数的内容视为sql指令的一部分来处理,而是在数据库完成sql指令编译之后,才能套用参数运行,因此就算参数中包含有破坏性的指令,也不会被数据库所运行。如果只传入一个参数,则名字可以随意取。

${}分析

<?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.mybatis.dao.IUserDao">
    <select id="findUserByName" resultType="user" useCache="true" parameterType="java.lang.String">
        select * from user where name like '%${name}%'
    </select>

${name}会被传入的内容替换掉,这里是所有内容直接替换,不会加上引号。实现代码如下:

package com.mybatis.test;

import com.mybatis.dao.IUserDao;
import com.mybatis.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class UserTest {
    private InputStream inputStream;
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    private IUserDao iUserDao;
    @Before
    public void init() throws IOException {
        inputStream=Resources.getResourceAsStream("mybatis-config.xml");
        sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        iUserDao=sqlSession.getMapper(IUserDao.class);
    }

    @After
    public void destory() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    //通过姓名模糊查找用户
    @Test
    public void testFindUserByName(){
        sqlSession.clearCache();
        List<User> users = iUserDao.findUserByName("' or '1'='1 #name=xxx and password=xxx");
        for (User user:users){
            System.out.println(user);
        }
    }
}

例如传入的内容是or ‘1’='1 #name=xxx and password=xxx则sql语句会变为:

select * from user where name like '%' or '1'='1 #name=xxx and password=xxx%'

从log4j看执行过程

2021-01-13 10:20:07,144 521    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 1309238149.
2021-01-13 10:20:07,144 521    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4e096385]
2021-01-13 10:20:07,147 524    [           main] DEBUG is.dao.IUserDao.findUserByName  - ==>  Preparing: select * from user where name like '%' or '1'='1%'
2021-01-13 10:20:07,178 555    [           main] DEBUG is.dao.IUserDao.findUserByName  - ==> Parameters: 
2021-01-13 10:20:07,200 577    [           main] DEBUG is.dao.IUserDao.findUserByName  - <==      Total: 4
User{id=2, name='积木', salary=23.56, address='江苏'}
User{id=3, name='刘德华', salary=36.65, address='上海'}
User{id=4, name='积木', salary=23.56, address='湖北'}
User{id=5, name='刘德华', salary=36.65, address='深圳'}
2021-01-13 10:20:07,207 584    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4e096385]
2021-01-13 10:20:07,208 585    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4e096385]
2021-01-13 10:20:07,208 585    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 1309238149 to pool.

可以看到在使用 的 方 式 之 后 , 就 没 有 了 P r e p a r e d S t a t e m e n t 预 编 译 的 过 程 , 向 ′ {}的方式之后,就没有了PreparedStatement预编译的过程,向'% PreparedStatement{name}%‘里面传递’ or ‘1’='1 #name=xxx and password=xxx,这样#后面的内容就被注解掉了,这句话就相当于select * from user where name like ‘%’ or ‘1’=‘1%’,即我们在语句中使用${}时,就可以无视name查询出所有的用户信息了,这就是sql注入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值