Spring—Spring 的優秀工具類盤點,第 2 部分: 特殊字元轉義和方法入參偵查工具類

來源:互聯網
上載者:User

Spring 不但提供了一個功能全面的應用開發架構,本身還擁有眾多可以在程式編寫時直接使用的工具類,您不但可以在 Spring 應用中使用這些工具類,也可以在其它的應用中使用,這些工具類中的大部分是可以在脫離 Spring 架構時使用的。瞭解 Spring 中有哪些好用的工具類並在程式編寫時適當使用,將有助於提高開發效率、增強代碼品質。

在這個分為兩部分的文章中,我們將從眾多的 Spring 工具類中遴選出那些好用的工具類介紹給大家。第 1 部分 介紹了與檔案資源操作和 Web 相關的工具類。在第 2 部分中將介紹特殊字元轉義和方法入參偵查工具類。

特殊字元轉義

由於 Web 應用程式需要聯合使用到多種語言,每種語言都包含一些特殊的字元,對於動態語言或標籤式的語言而言,如果需要動態構造語言的內容時,一個我們經常會碰到的問題就是特殊字元轉義的問題。下面是 Web 開發人員最常面對需要轉義的特殊字元類型:

HTML 特殊字元;
JavaScript 特殊字元;
SQL 特殊字元;
如果不對這些特殊字元進行轉義處理,則不但可能破壞文檔結構,還可以引發潛在的安全問題。Spring 為 HTML 和 JavaScript 特殊字元提供了轉義操作工具類,它們分別是 HtmlUtils 和 JavaScriptUtils。

HTML 特殊字元轉義

HTML 中 <,>,& 等字元有特殊含義,它們是 HTML 語言的保留字,因此不能直接使用。使用這些個字元時,應使用它們的逸出序列:

&:&amp;
" :&quot;
< :&lt;
> :&gt;
由於 HTML 網頁本身就是一個文本型結構化文檔,如果直接將這些包含了 HTML 特殊字元的內容輸出到網頁中,極有可能破壞整個 HTML 文檔的結構。所以,一般情況下需要對動態資料進行轉義處理,使用逸出序列表示 HTML 特殊字元。下面的 JSP 網頁將一些變數動態輸出到 HTML 網頁中:


清單 1. 未進行 HTML 特殊字元轉義處理網頁
               
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%!
   String userName = "</td><tr></table>";
   String address = " /" type=/"button";
 %>
<table border="1">
   <tr>
     <td>姓名:</td><td><%=userName%></td> ①
   </tr>
   <tr>
     <td>年齡:</td><td>28</td>
   </tr>
</table>
 <input value="<%=address%>"  type="text" /> ②
 


在 ① 和 ② 處,我們未經任何轉義處理就直接將變數輸出到 HTML 網頁中,由於這些變數可能包含一些特殊的 HTML 的字元,它們將可能破壞整個 HTML 文檔的結構。我們可以從以上 JSP 頁面的一個具體輸出中瞭解這一問題:

<table border="1">
   <tr>
     <td>姓名:</td><td></td><tr></table></td>
     ① 破壞了 <table> 的結構
   </tr>
   <tr>
     <td>年齡:</td><td>28</td>
   </tr>
</table>
 <input value=" " type="button"  type="text" />
 ② 將本來是輸入框組件偷梁換柱為按鈕組件
 


融合動態資料後的 HTML 網頁已經面目全非,首先 ① 處的 <table> 結構被包含 HTML 特殊字元的 userName 變數截斷了,造成其後的 <table> 代碼變成無效的內容;其次,② 處 <input> 被動態資料改換為按鈕類型的組件(type="button")。為了避免這一問題,我們需要事先對可能破壞 HTML 文檔結構的動態資料進行轉義處理。Spring 為我們提供了一個簡單適用的 HTML 特殊字元轉義工具類,它就是 HtmlUtils。下面,我們通過一個簡單的例子瞭解 HtmlUtils 的具體用法:


清單 2. HtmpEscapeExample
               
package com.baobaotao.escape;
import org.springframework.web.util.HtmlUtils;
public class HtmpEscapeExample {
    public static void main(String[] args) {
        String specialStr = "<div id=/"testDiv/">test1;test2</div>";
        String str1 = HtmlUtils.htmlEscape(specialStr); ①轉換為HTML逸出字元表示
        System.out.println(str1);
      
        String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); ②轉換為資料轉義表示
        System.out.println(str2);
      
        String str3 = HtmlUtils.htmlEscapeHex(specialStr); ③轉換為十六進位資料轉義表示
        System.out.println(str3);
      
        ④下面對轉義後字串進行反向操作
        System.out.println(HtmlUtils.htmlUnescape(str1));
        System.out.println(HtmlUtils.htmlUnescape(str2));
        System.out.println(HtmlUtils.htmlUnescape(str3));
    }
}
 


HTML 不但可以使用通用的逸出序列表示 HTML 特殊字元,還可以使用以 # 為首碼的數字序列表示 HTML 特殊字元,它們在最終的顯示效果上是一樣的。HtmlUtils 提供了三個轉義方法:

方法 說明
static String htmlEscape(String input)  將 HTML 特殊字元轉義為 HTML 通用逸出序列;
static String htmlEscapeDecimal(String input)  將 HTML 特殊字元轉義為帶 # 的十進位資料逸出序列;
static String htmlEscapeHex(String input)  將 HTML 特殊字元轉義為帶 # 的十六進位資料逸出序列;

此外,HtmlUtils 還提供了一個能夠將經過轉義內容還原的方法:htmlUnescape(String input),它可以還原以上三種逸出序列的內容。運行以上代碼,您將可以看到以下的輸出:

str1:&lt;div id=&quot;testDiv&quot;&gt;test1;test2&lt;/div&gt;
str2:&#60;div id=&#34;testDiv&#34;&#62;test1;test2&#60;/div&#62;
str3:&#x3c;div id=&#x22;testDiv&#x22;&#x3e;test1;test2&#x3c;/div&#x3e;
<div id="testDiv">test1;test2</div>
<div id="testDiv">test1;test2</div>
<div id="testDiv">test1;test2</div>
 


您只要使用 HtmlUtils 對代碼 清單 1 的 userName 和 address 進行轉義處理,最終輸出的 HTML 頁面就不會遭受破壞了。

JavaScript 特殊字元轉義

JavaScript 中也有一些需要特殊處理的字元,如果直接將它們嵌入 JavaScript 代碼中,JavaScript 程式結構將會遭受破壞,甚至被嵌入一些惡意的程式。下面列出了需要轉義的特殊 JavaScript 字元:

' :/'
" :/"
/ ://
走紙換頁: /f
換行:/n
換欄符:/t
斷行符號:/r
回退符:/b

我們通過一個具體例子示範動態變數是如何對 JavaScript 程式進行破壞的。假設我們有一個 JavaScript 陣列變數,其元素值通過一個 Java List 對象提供,下面是完成這一操作的 JSP 代碼片斷:


清單 3. jsTest.jsp:未對 JavaScript 特殊字元進行處理
               
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<jsp:directive.page import="java.util.*"/>
<%
  List textList = new ArrayList();
  textList.add("/";alert();j=/"");
%>
<script>
  var txtList = new Array();
   <% for ( int i = 0 ; i < textList.size() ; i++) { %>
     txtList[<%=i%>] = "<%=textList.get(i)%>";
  ① 未對可能包含特殊 JavaScript 字元的變數進行處理
   <% } %>
</script>
 


當用戶端調用這個 JSP 頁面後,將得到以下的 HTML 輸出頁面:

<script>
  var txtList = new Array();
   txtList[0] = "";alert();j=""; ① 本來是希望接受一個字串,結果被植入了一段JavaScript代碼
</script>
 


由於包含 JavaScript 特殊字元的 Java 變數直接合并到 JavaScript 代碼中,我們本來期望 ① 處所示部分是一個普通的字串,但結果變成了一段 JavaScript 代碼,網頁將彈出一個 alert 視窗。想像一下如果粗體部分的字串是“";while(true)alert();j="”時會產生什麼後果呢。

因此,如果網頁中的 JavaScript 代碼需要通過拼接 Java 變數動態產生時,一般需要對變數的內容進行轉義處理,可以通過 Spring 的 JavaScriptUtils 完成這件工作。下面,我們使用 JavaScriptUtils 對以上代碼進行改造:

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<jsp:directive.page import="java.util.*"/>
<jsp:directive.page import="org.springframework.web.util.JavaScriptUtils"/>
<%
  List textList = new ArrayList();
  textList.add("/";alert();j=/"");
%>
<script>
   var txtList = new Array();
   <% for ( int i = 0 ; i < textList.size() ; i++) { %>
   ① 在輸出動態內容前事先進行轉義處理
   txtList[<%=i%>] = "<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";
   <% } %>
</script>
 


通過轉義處理後,這個 JSP 頁面輸出的結果網頁的 JavaScript 代碼就不會產生問題了:

<script>
   var txtList = new Array();
   txtList[0] = "/";alert();j=/"";
   ① 粗體部分僅是一個普通的字串,而非一段 JavaScript 的語句了
</script>
 


SQL特殊字元轉義

應該說,您即使沒有處理 HTML 或 JavaScript 的特殊字元,也不會帶來災難性的後果,但是如果不在動態構造 SQL 陳述式時對變數中特殊字元進行處理,將可能導致程式漏洞、資料盜取、資料破壞等嚴重的安全問題。網路中有大量講解 SQL 注入的文章,感興趣的讀者可以搜尋相關的資料深入研究。

雖然 SQL 注入的後果很嚴重,但是只要對動態構造的 SQL 陳述式的變數進行特殊字元轉義處理,就可以避免這一問題的發生了。來看一個存在安全性漏洞的經典例子:

SELECT COUNT(userId)
FROM t_user
WHERE userName='"+userName+"' AND password ='"+password+"';
 


以上 SQL 陳述式根據返回的結果數判斷使用者提供的登入資訊是否正確,如果 userName 變數不經過特殊字元轉義處理就直接合并到 SQL 陳述式中,駭客就可以通過將 userName 設定為 “1' or '1'='1”繞過使用者名稱/密碼的檢查直接進入系統了。

所以除非必要,一般建議通過 PreparedStatement 參數綁定的方式構造動態 SQL 陳述式,因為這種方式可以避免 SQL 注入的潛在安全問題。但是往往很難在應用中完全避免通過拼接字串構造動態 SQL 陳述式的方式。為了防止他人使用特殊 SQL 字元破壞 SQL 的語句結構或植入惡意操作,必須在變數拼接到 SQL 陳述式之前對其中的特殊字元進行轉義處理。Spring 並沒有提供相應的工具類,您可以通過 jakarta commons lang 通用類包中(spring/lib/jakarta-commons/commons-lang.jar)的 StringEscapeUtils 完成這一工作:


清單 4. SqlEscapeExample
               
package com.baobaotao.escape;
import org.apache.commons.lang.StringEscapeUtils;
public class SqlEscapeExample {
    public static void main(String[] args) {
        String userName = "1' or '1'='1";
        String password = "123456";
        userName = StringEscapeUtils.escapeSql(userName);
        password = StringEscapeUtils.escapeSql(password);
        String sql = "SELECT COUNT(userId) FROM t_user WHERE userName='"
            + userName + "' AND password ='" + password + "'";
        System.out.println(sql);
    }
}
 


事實上,StringEscapeUtils 不但提供了 SQL 特殊字元轉義處理的功能,還提供了 HTML、XML、JavaScript、Java 特殊字元的轉義和還原的方法。如果您不介意引入 jakarta commons lang 類包,我們更推薦您使用 StringEscapeUtils 工具類完成特殊字元轉義處理的工作。

 

 


 回頁首
 

 

方法入參偵查工具類

Web 應用程式在接受表單提交的資料後都需要對其進行合法性檢查,如果表單資料不合法,請求將被駁回。類似的,當我們在編寫類的方法時,也常常需要對方法入參進行合法性檢查,如果入參不符合要求,方法將通過拋出異常的方式拒絕後續處理。舉一個例子:有一個根據檔案名稱擷取輸入資料流的方法:InputStream getData(String file),為了使方法能夠成功執行,必須保證 file 入參不能為 null 或空白字元,否則根本無須進行後繼的處理。這時方法的編寫者通常會在方法體的最前面編寫一段對入參進行檢測的代碼,如下所示:

public InputStream getData(String file) {
    if (file == null || file.length() == 0|| file.replaceAll("//s", "").length() == 0) {
        throw new IllegalArgumentException("file入參不是有效檔案地址");
    }

}
 


類似以上檢測方法入參的代碼是非常常見,但是在每個方法中都使用手工編寫檢測邏輯的方式並不是一個好主意。閱讀 Spring 源碼,您會發現 Spring 採用一個 org.springframework.util.Assert 通用類完成這一任務。

Assert 翻譯為中文為“斷言”,使用過 JUnit 的讀者都熟知這個概念,它斷定某一個實際的運行值和預期想一樣,否則就拋出異常。Spring 對方法入參的檢測借用了這個概念,其提供的 Assert 類擁有眾多按規則對方法入參進行斷言的方法,可以滿足大部分方法入參檢測的要求。這些斷言方法在入參不滿足要求時就會拋出 IllegalArgumentException。下面,我們來認識一下 Assert 類中的常用斷言方法:

斷言方法 說明
notNull(Object object)  當 object 不為 null 時拋出異常,notNull(Object object, String message) 方法允許您通過 message 定製異常資訊。和 notNull() 方法斷言規則相反的方法是 isNull(Object object)/isNull(Object object, String message),它要求入參一定是 null;
isTrue(boolean expression) / isTrue(boolean expression, String message)  當 expression 不為 true 拋出異常;
notEmpty(Collection collection) / notEmpty(Collection collection, String message)  當集合未包含元素時拋出異常。notEmpty(Map map) / notEmpty(Map map, String message) 和 notEmpty(Object[] array, String message) / notEmpty(Object[] array, String message) 分別對 Map 和 Object[] 類型的入參進行判斷;
hasLength(String text) / hasLength(String text, String message)  當 text 為 null 或長度為 0 時拋出異常;
hasText(String text) / hasText(String text, String message)  text 不能為 null 且必須至少包含一個非空格的字元,否則拋出異常;
isInstanceOf(Class clazz, Object obj) / isInstanceOf(Class type, Object obj, String message)  如果 obj 不能被正確造型為 clazz 指定的類將拋出異常;
isAssignable(Class superType, Class subType) / isAssignable(Class superType, Class subType, String message)  subType 必須可以按類型匹配於 superType,否則將拋出異常;

使用 Assert 斷言類可以簡化方法入參檢測的代碼,如 InputStream getData(String file) 在應用 Assert 斷言類後,其代碼可以簡化為以下的形式:

public InputStream getData(String file){
    Assert.hasText(file,"file入參不是有效檔案地址");
    ① 使用 Spring 斷言類進行方法入參檢測

}
 


可見使用 Spring 的 Assert 替代自編碼實現的入參檢測邏輯後,方法的簡潔性得到了不少的提高。Assert 不依賴於 Spring 容器,您可以大膽地在自己的應用中使用這個工具類。

 

 


 回頁首
 

 

小結

本文介紹了一些常用的 Spring 工具類,其中大部分 Spring 工具類不但可以在基於 Spring 的應用中使用,還可以在其它的應用中使用。

對於 Web 應用程式來說,由於有很多關聯的指令碼代碼,如果這些代碼通過拼接字串的方式動態產生,就需要對動態內容中特殊的字元進行轉義處理,否則就有可能產生意想不到的後果。Spring 為此提供了 HtmlUtils 和 JavaScriptUtils 工具類,只要將動態內容在拼接之前使用工具類進行轉義處理,就可以避免類似問題的發生了。如果您不介意引入一個第三方類包,那麼 jakarta commons lang 通用類包中的 StringEscapeUtils 工具類可能更加適合,因為它提供了更加全面的轉義功能。

最後我們還介紹了 Spring 的 Assert 工具類,Assert 工具類是通用性很強的工具類,它使用物件導向的方式解決方案入參檢測的問題,您可以在自己的應用中使用 Assert 對方法入參進行檢查。

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/oycn0755/archive/2008/01/15/2045305.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.