【spring】jdbcTemplate之sql参数注入

demo
@Repository("jdbcDao")
public class JdbcTemplateDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate namedTemplate;
    private final static List<String> names = new ArrayList<String>();
    private final String childAge = "5";
    private final String parentId = "2";
    static {
        names.add("吴三");
        names.add("吴二");
    }
}
<bean id="dataSource" ...> </bean >
 
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="false" lazy-init="false" autowire="default" >
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- NamedParameterJdbcTemplate的构造函数有2种。1.DataSource;2.JdbcOperations -->
<bean id="namedTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" abstract="false" lazy-init="false" autowire="default" >
	<constructor-arg type="javax.sql.DataSource" ref="dataSource" />
	<!--constructor-arg type="org.springframework.jdbc.core.JdbcOperations" ref="jdbcTemplate" / -->
</bean>
1、数组形式的参数

  优势: 针对简单sql,能够节约部份内存空间。减小代码量、易读。html

  缺点:java

    一、相同的参数不能够复用,让array占用更多的空间。(固然,以如今的硬件来讲,这点空间/内存、多余添加数组值花费的时间 彻底微不足道)spring

    二、若是是in的参数,要动态拼接(?)占位符。(我的认为最麻烦、繁琐的,扩展:oracle对in最大支持1000)sql

    三、若是sql中参数过多,其实很差阅读修改。数据库

   /** * 常规?占位参数;<br> * 问题:<br> * 一、若是参数是in,那么须要本身动态的添加占位符?。明显这很麻烦。<br> * 二、若是参数屡次出现,那么数组中也要重复出现。明显这很浪费空间。<br> */
    public List<Child> arrayParam() {
        List<Object> params = new ArrayList<Object>();
        String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c";
        sql += " where c.child_age=? and c.parent_id = ?";
        params.add(childAge);
        params.add(parentId);
        //若是是in参数,拼接?占位符很麻烦。
        sql += " and c.child_name in(";
        for (Iterator<String> iterator = names.iterator(); iterator.hasNext(); ) {
            iterator.next();
            sql += "?";
            if(iterator.hasNext()) sql += ",";
        }
        sql += ")";
        params.addAll(names);
        return this.jdbcTemplate.query(sql,params.toArray(),new BeanPropertyRowMapper<Child>(Child.class));
    }

我的习惯用List添加参数,而后再把List转换成Array。数组

好处是:若是用数组,当sql存在动态条件,那么没法肯定数组长度。而用List就不须要本身去维护。安全

 

2、map形式的参数

  优势:oracle

    一、解决了in参数的问题。app

    二、参数值能够复用。函数

   /** * map实现别名参数。<br/> * 解决:<br/> * 一、相对array参数,解决了参数in复杂、变量重用的问题。<br/> * 问题:<br/> * 一、若是是in貌似是不能够用数组的,用list能够。<br/> * 二、比较麻烦的是NamedParameterJdbcTemplate和JdbcTemplate没继承/接口关系。而且Named依赖Jdbc,因此在写公共dao的时候要注意。 * @return */
    public List<Child> mapParam(){
        Map<String,Object> params = new HashMap<String,Object>();
        String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c";
        sql += " where c.child_age=:age and c.parent_id =:id and c.child_name in(:names)";
        params.put("age",childAge);
        params.put("id",parentId);
        params.put("names",names);
        return namedTemplate.query(sql,params,new BeanPropertyRowMapper<Child>(Child.class));
    }

能够看出对in的参数形式支持很友好,查询条件也能够复用。但我遇到一个问题是:参数是in,在map不能用数组形式,用List是能够的。

特别:NamedParameterJdbcTemplate与jdbcTemplate没有任何的继承/实现关系(Named能够经过DataSource、JdbcTemplate来生成)。因此再写公共的父类dao时,要想一下怎么写。

3、javaBean形式的参数
 /** * javaBean参数。<br></> * 要引入辅助的javaBean。 * @return */
    public List<Child> beanParam(){
        String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c";
        sql += " where c.child_age=:childAge and c.parent_id =:parentId ";
        sql += " and c.child_name in(:names)";
        ParamBean bean = new ParamBean();
        bean.setChildAge(childAge);
        bean.setParentId(parentId);
        bean.setNames(names);
        SqlParameterSource param = new BeanPropertySqlParameterSource(bean);

        return namedTemplate.query(sql,param,new BeanPropertyRowMapper<Child>(Child.class));
    }

简单浏览了下源码,感受就是利用反射找到属性名。而后处理和map形式的同样。

此形式相对map来讲,只在因而用Map仍是JavaBean。

表面上的区别就是:若是参数是经过JavaBean传到dao层,那么不用把bean转换成map。相对的若是是经过map传到dao层的,也用map形式也无需转换成javaBean。

4、什么是防止sql注入?(我的很简单的理解)

假设sql: select * from child c where c.id = ? ;

若是在dao层为了贪图方便,或者没有防止sql注入的概念(就是安全性问题)。在dao层的代码:

  String sql = "select * from child c where c.id='"+id+"'";

假设指望的id都是1,2,3等自增的数字字符串。

可是,此sql最后多是任何形式。好比恶意攻击时,最终sql: select * from child c where c.id='100';delete from child; --'

红色下划线部分就是id的值(--在sql中是注释,把最后一个引号注释掉)。

那么会删除整个表child的数据,这显然是不安全的。

 

 

我简单测试了下,数据库是oracle。不论是jdbcTemplate、纯jdbc、hibernate都不存在上诉问题(猜测是oracle驱动jar中处理了)。会抛一个异常出来:

java.sql.SQLException: ORA-00911: 无效字符
	at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439)
	at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395)
	at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802)
	at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436)
	at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186)
	at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521)
	at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205)
	at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:861)
	at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1145)
	at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1267)
	at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449)
	at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3493)
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1203)
	at com.vergilyn.test.sh.dao.JdbcTemplateDao.injectionAttack(JdbcTemplateDao.java:49)
	at com.lyn.Junit.TestJdbc.testInjectionAttack(TestJdbc.java:35)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

测试代码:

    @Test
    @Rollback(true)
    public void testInjectionAttack(){ //结论
         String id = "1";
        id = "1';delete from child where child_id='1';--";
        jdbcDao.injectionAttack(id);
    }

    @Test
    @Rollback(true)
    public void testSqlInjectionAttack(){ //结论 jdbcTemplate不会存在此问题
         String id = "1";
        id = "1';delete from child where child_id='1';--";
        Child rs = jdbcDao.sqlInjectionAttack(id);
        System.out.println(JSON.toJSONString(rs));
    }
public void injectionAttack(String id) {
       Connection con = null;// 建立一个数据库链接
        PreparedStatement pre = null;// 建立预编译语句对象,通常都是用这个而不用Statement
       ResultSet result = null;// 建立一个结果集对象
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");// 加载Oracle驱动程序
            String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
            String user = "vergilyn";
            String password = "409839163";
            con = DriverManager.getConnection(url, user, password);// 获取链接

            String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c";
          sql += " where c.child_id= '"+id+"'";
          pre = con.prepareStatement(sql);// 实例化预编译语句
            result = pre.executeQuery();// 执行查询,注意括号中不须要再加参数
            while (result.next()){
                // 当结果集不为空时
                System.out.println("姓名:" + result.getString("child_Name") );
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try
            {
                // 逐一将上面的几个对象关闭,由于不关闭的话会影响性能、而且占用资源
                // 注意关闭的顺序,最后使用的最早关闭
                if (result != null) result.close();
                if (pre != null) pre.close();
                if (con != null) con.close();
                System.out.println("数据库链接已关闭!");
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    /**
     * 测试sql注入攻击。jdbcTemplate不会存在此安全问题。
     * @param id
     * @return
     */
    public Child sqlInjectionAttack(String id){
        String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c";
        sql += " where c.child_id= '"+id+"'";
        return this.jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Child>(Child.class));
    }

正确sql:

  select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= '1'

攻击sql:

  select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= '1';delete from child where child_id='1';--'

针对攻击sql会抛出上面的异常,我把此sql在PL/SQL中是能够运行的。那么,我的猜测是oracle的驱动jar中进行处理了。

(能够看下如何防止sql注入攻击,网上不少。虽然通过上面的测试,举例的状况在测试时不会出现。但能够详细了解下,还可能在什么状况下出现SQL注入攻击

百度baike: SQL注入攻击

 

2016-12-22 以上说的: oracle的驱动jar中处理了sql注入攻击是错误的

【spring】(填坑)sql注入攻击 - 持久层参数化 (验证了错误,并没看懂源代码)

相关文章
相关标签/搜索