【spring】jdbcTemplate之sql參數注入

來源:互聯網
上載者:User

標籤:安全性   user   iterator   epo   manage   問題:   manager   gil   baidu   

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注入攻擊

【spring】jdbcTemplate之sql參數注入

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.