Java StringEscapeUtils類的轉義與反轉義方法
org.apache.commons.lang.StringEscapeUtils
類可以對js sql html xml等代碼進行轉義!用來處理前端js注入代碼
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 容器,您可以大膽地在自己的應用中使用這個工具類
--------------code--------------
import org.apache.commons.lang.StringEscapeUtils; public class EscapeString { public static void main(String[] args) throws Exception { String str = "中國"; System.out.println("用escapeJava方法轉義之后的字符串為:"+StringEscapeUtils.escapeJava(str)); System.out.println("用unescapeJava方法反轉義之后的字符串為:"+StringEscapeUtils.unescapeJava(StringEscapeUtils.escapeJava(str))); System.out.println("用escapeHtml方法轉義之后的字符串為:"+StringEscapeUtils.escapeHtml(str)); System.out.println("用unescapeHtml方法反轉義之后的字符串為:"+StringEscapeUtils.unescapeHtml(StringEscapeUtils.escapeHtml(str))); System.out.println("用escapeXml方法轉義之后的字符串為:"+StringEscapeUtils.escapeXml(str)); System.out.println("用unescapeXml方法反轉義之后的字符串為:"+StringEscapeUtils.unescapeXml(StringEscapeUtils.escapeXml(str))); System.out.println("用escapeJavaScript方法轉義之后的字符串為:"+StringEscapeUtils.escapeJavaScript(str)); System.out.println("用unescapeJavaScript方法反轉義之后的字符串為:"+StringEscapeUtils.unescapeJavaScript(StringEscapeUtils.escapeJavaScript(str))); /**輸出結果如下: 用escapeJava方法轉義之后的字符串為:/u4E2D/u56FD/u5171/u4EA7/u515A 用unescapeJava方法反轉義之后的字符串為:中國 用escapeHtml方法轉義之后的字符串為:中國 用unescapeHtml方法反轉義之后的字符串為:中國 用escapeXml方法轉義之后的字符串為:中國 用unescapeXml方法反轉義之后的字符串為:中國 用escapeJavaScript方法轉義之后的字符串為:/u4E2D/u56FD/u5171/u4EA7/u515A 用unescapeJavaScript方法反轉義之后的字符串為:中國*/ } }