【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注入攻擊 - 持久層參數化 (驗證了錯誤,並沒看懂源代碼)

相關文章
相關標籤/搜索