# 预防 SQL 注入

### 什么是 SQL 注入攻击

假设需要按照商品 title 进行筛选数据，MyBatis\src\main\resources\mappers\goods.xml 下的  \<mapper namespace="goods">这么写:

```xml
    <select id="selectByTitle" parameterType="java.util.Map" resultType="indi.chester.mybatis.entity.Goods">
        <!-- ${} 文本替换, 未经任何处理对SQL文本替换, SQL 注入攻击 -->
        SELECT  * FROM t_goods WHERE  title=${title} LIMIT 5
    <mapper namespace="goods">
        
    </select>
```

但如果把传入的title 参数换成"'' or 1=1 or 1=''"  串换成这样：

```sql
SELECT  * FROM t_goods 
    WHERE  title="'' or 1=1 or 1=''"  LIMIT 5
```

那么 title 的判别条件就会恒成立，因为内部有一个 "1=1"。 这样一句 SQL 语句就可以把数据库所有数据一次性全部提取出来(如果删掉 LIMIT 5)。这种如熬过条件约束，越权获取数据的方实 这就是 SQL 注入攻击。

### 解决方法

解决方法也很简单, 那就是将 ${参数名} 换成 #{参数名}， 则会在运行时预编译，将'' or 1=1 or 1=''  看作一整个字符串，而不是运行这个字符串。

```xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="indi.chester.mybatis.entity.Goods">
       
        <!-- #{} 预编译传值, 防止 SQL 注入攻击 -->
        SELECT  * FROM  t_goods WHERE  title=#{title} 
 
    </select>
```

这样如果还是 将 title 参数设置成'' or 1=1 or 1=''  则无法继续访问数据, 除非有一条数据的 title 就是 '' or 1=1 or 1='' .

### 有的时候需要 ${参数名}

有的时候我们也需要将传入的参数组织成一部分 SQL 语句来运行，而不希望将他看作一个参数。比如我们需要按照 goods\_id 降序排序，则需要将 "ORDER BY goods\_id DESC"  组织成一段 SQL 语句，而不是当作一个参数。MyBatis\src\main\resources\mappers\goods.xml 下的  \<mapper namespace="goods">这么写:

```xml
    <select id="selectByTitle" parameterType="java.util.Map" resultType="indi.chester.mybatis.entity.Goods">
 
        <!-- 有的时候 使用原文 -->
        SELECT  * FROM  t_goods WHERE  title=#{title} ${order}
    </select>
```

在 MyBatis\src\test\java\MyBatisTest.java 添加下面这个方法：

```java
    @Test
    public void testSelectByTitle() throws Exception{
        SqlSession sqlSession=null;
        try {
            sqlSession=MyBatisUtils.openSession();
            Map parm = new HashMap();
            //尝试 SQL 注入攻击
            //parm.put("title", "'' or 1=1 or 1=''");
            
            //有的时候 使用原文
            parm.put("title", "亲润 孕妇护肤品豆乳大米盈润保湿胶原蚕丝面膜（18片装）");
            parm.put("order", "ORDER BY goods_id DESC");
            List<Goods> goodsList= sqlSession.selectList("goods.selectByTitle", parm);
            for (Goods g: goodsList){
                System.out.println(g.getGoodsId() +" " + g.getTitle());
            }

        }catch (Exception e){
            if (sqlSession!=null){
                sqlSession.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

```

运行结果:

```
21:25:14:725 [main]  DEBUG goods.selectByTitle -==>  Preparing: SELECT * FROM t_goods WHERE title=? ORDER BY goods_id DESC  
21:25:14:755 [main]  DEBUG goods.selectByTitle -==> Parameters: 亲润 孕妇护肤品豆乳大米盈润保湿胶原蚕丝面膜（18片装）(String) 
21:25:14:784 [main]  DEBUG goods.selectByTitle -<==      Total: 3 
1287 亲润 孕妇护肤品豆乳大米盈润保湿胶原蚕丝面膜（18片装）
1265 亲润 孕妇护肤品豆乳大米盈润保湿胶原蚕丝面膜（18片装）
739 亲润 孕妇护肤品豆乳大米盈润保湿胶原蚕丝面膜（18片装）

```
