【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>
一、数组形式的参数
优点: 针对简单sql,可以节约部分内存空间。减少代码量、易读。
缺点:
1、相同的参数不可以复用,让array占用更多的空间。(当然,以现在的硬件来说,这点空间/内存、多余添加数组值花费的时间 完全微不足道)
2、如果是in的参数,要动态拼接(?)占位符。(个人认为最麻烦、繁琐的,扩展:oracle对in最大支持1000)
3、如果sql中参数过多,其实不好阅读修改。
/** * 常规?占位参数;<br> * 问题:<br> * 1、如果参数是in,那么需要自己动态的添加占位符?。明显这很麻烦。<br> * 2、如果参数多次出现,那么数组中也要重复出现。明显这很浪费空间。<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就不需要自己去维护。
二、map形式的参数
优点:
1、解决了in参数的问题。
2、参数值可以复用。
/** * map实现别名参数。<br/> * 解决:<br/> * 1、相对array参数,解决了参数in复杂、变量重用的问题。<br/> * 问题:<br/> * 1、如果是in貌似是不可以用数组的,用list可以。<br/> * 2、比较麻烦的是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时,要想一下怎么写。
三、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。
四、什么是防止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注入攻击 - 持久层参数化 (验证了错误,并没看懂源代码)
Github: https://github.com/vergilyn
出处: http://www.cnblogs.com/VergiLyn/
备注: 一只凄惨的中华田园犬.