Mybatis学习笔记-11 取值符号$、#以及SQL注入问题

区别1:

当有@Parm("xx")指明参数名称时,对于数值型数据,#和$不存在实质性的区别,对于字符型数据,$取出的值是不会自带引号''的。

数值型实验:

1. 在DAO中添加方法

User queryUserById(@Param("id") Integer id);

2. 编写Mapper.xml

 A. 采用#取值

    <sql id="user_field">
        select id,username,password,gender,regist_time as registerTime
        from t_user
    </sql>
    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = #{id}
    </select>

B. 采用$取值

    <sql id="user_field">
        select id,username,password,gender,regist_time as registerTime
        from t_user
    </sql>
    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = concat("'",${id},"'")
    </select>

[注意!!!]:通过控制台输出的SQL语句我们也可以发现:

这是#输出的SQL;

 这是$输出的SQL;

$做的是直接的字符串拼接,而#显然不是,是采用占位符的方式,它会根据是否是字符型选择是否自行添加' '。

字符型实验:

1. 在DAO中添加方法

User queryUserByUsername(@Param("username") String username);

2. 编写Mapper.xml

 A. 采用#取值

    <select id="queryUserByUsername" resultType="User">
        <include refid="user_field"></include>
        where username = #{username}
    </select>

B. 采用$取值

    <select id="queryUserByUsername" resultType="User">
        <include refid="user_field"></include>
        where username = ${username}
    </select>

正确写法:

    <select id="queryUserByUsername" resultType="User">
        <include refid="user_field"></include>
        where username = '${username}'
    </select>

区别2:

在没有使用@Param注解时,#支持零散参数用arg0、arg1或者param1、param2,但是$不支持。使用@Param时,#和$都支持param。

A. @Param注解 + #{param1}

    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = #{param1}
    </select>

B. @Param注解 + ${param}

    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = ${param1}
    </select>

区别依旧是SQL语句的不同,如果读者还有印象的话,PreparedStatement主要的作用就是防止SQL注入,后面会说。

需要注意:如果参数使用了@Param注解,则#只支持param而不支持arg。

C. 去除@Param注解

1)#{arg0}或#{param1}

User queryUserById(Integer id);
    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = #{arg0}
    </select>
    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = #{param1}
    </select>

2)${arg0}或${param1}

    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = ${arg0}
    </select>

由报错信息我们可以看到,去除@Param注解后,${arg0}实际上将arg0当作是类参数Integer id的一个名叫arg0的属性,企图通过get方法取值。

    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = ${param1}
    </select>

由报错信息我们可以看到,去除@Param注解后,${param1}实际上将param1当作是类参数Integer id的一个名叫param1的属性,企图通过get方法取值。

值得注意:在Mybatis中,即便我们在DAO接口中设置的方法的参数是基本数据类型,在Mybatis生成时依旧会将参数变为基本数据类型的包装类,如:

User queryUserById(int id);
    <select id="queryUserById" resultType="User">
        <include refid="user_field"></include>
        where id = ${id}
    </select>

[总结]:如果一定要使用$,那么对于零散的参数,一定要加注解。对于字符型或字符串,需要手动添加引号。

区别3[重要]:

 SQL注入指的是我们可以通过SQL语句,绕开仅仅使用SQL语句做的安全检查。例如我们要登陆系统,一般都是验证用户名或密码。

我们要执行的正常SQL语句:"select * from t_user where username = ' " + username + " '  and password = ' " + password + " ' "。

但是如果我们将password赋值为'123456  or 1 = 1',那么执行的SQL语句会变成:

select * from t_user where username = ' " + username + " '  and 123456 or 1 = 1 ' ",由于1=1是成立条件,因此这条语句可以将表里的数据全部查出,这就会完全绕开用SQL做的安全检查。

我们做一个示例:

List<User> Login(@Param("username") String id,@Param("password") String password);
    <select id="Login" resultType="User">
        select * from t_user
        where username = '${username}' and password = '${password}'
    </select>
    @Test
    public void testLogin(){
        List<User> zzt = mapper.Login("zzt", "111' or '1' = '1");
        for (User user : zzt) {
            System.out.println(zzt);
        }
    }

细心的读者可能也发现了,我们这样做的前提是整个方法的返回必须是个List,如果是实体类的类型,则会因为要封装的对象太多导致错误。

但是这并不代表着我们就不需要担心SQL注入的问题,如果整个系统的登录逻辑是,只要查出来的系统的数据中存在超过一条,就可以放行,特别是对于一些账号和密码允许重复,即用其他值作为主键的情况。

另外,我们来谈一种更可能的情况,我们知道对于数据库的大量查询操作是相当费时间的,通过SQL注入我们可以查询所有的数据,那么只要有客户端不断向服务器端进行SQL注入,服务器可能会因为大量的查询导致缓存耗尽、影响系统响应甚至宕机的情况。

 那么#使用占位符为何能避免SQL注入的问题呢?我们看一眼SQL语句便知:

实际上翻译过来就是:select * from t_user where username = 'zzt' and password = "111' or '1' = '1 ", 也就是说,111' or '1' = '1被当作了密码,自然就防止了注入

但是!但是!但是!在某些情况下,也是不能使用占位符的,例如让数据按照动态的规则进行有序查询输出:select * from t_user order by id desc;

List<User> queryByDesc(@Param("order") String order);
    <select id="queryByDesc" resultType="User">
        select * from t_user order by id #{order}
    </select>
    @Test
    public void testQueryByDesc(){
        List<User> users = mapper.queryByDesc("desc");
        for (User user : users) {
            System.out.println(user);
        }
    }

显然:select * from t_user order by id 'desc';是有语法错误的。

使用拼接的方式:

    <select id="queryByDesc" resultType="User">
        select * from t_user order by id ${order}
    </select>

为了保证安全性,防止注入,我们不允许用户直接输入规则,只允许输入规则对应的参数:

    @Test
    public void testQueryByDesc(){
        int signal = 0;
        List<User> users;
        if( signal == 0 ){
            users = mapper.queryByDesc("desc");
        }else{
            users = mapper.queryByDesc("asc");
        }
        for (User user : users) {
            System.out.println(user);
        }
    }

[总结]:在要填充的数据与列属性相关时,用占位符确实可以规避SQL注入的风险,其他情况(与SQL本身有关)使用,则极有可能导致SQL语法错误。SQL拼接会有注入风险,但是只要语法保证正确就没有限制(此处限制指的是可以使用而不是防止注入导致的安全性等问题)。大部分情况下我们还是推荐使用#。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值