特殊字符处理_java Spring常用工具类
java防SQL注入html编码入侵特殊字符转义和方法入参检测工具(Spring)
Spring不但提供了一个功能全面的应用开发框架,本身还拥有众多可以在程序编写时直接使用的工具类,您不但可以在Spring应用中使用这些工具类,也可以在其它的应用中使用,这些工具类中的大部分是可以在脱离Spring框架时使用的。了解Spring中有哪些好用的工具类并在程序编写时适当使用,将有助于提高开发效率、增强代码质量。
在这个分为两部分的文章中,我们将从众多的Spring工具类中遴选出那些好用的工具类介绍给大家。第1部分介绍了与文件资源操作和Web相关的工具类。在第2部分中将介绍特殊字符转义和方法入参检测工具类。
特殊字符转义
由于Web应用程序需要联合使用到多种语言,每种语言都包含一些特殊的字符,对于动态语言或标签式的语言而言,如果需要动态构造语言的内容时,一个我们经常会碰到的问题就是特殊字符转义的问题。下面是Web开发者最常面对需要转义的特殊字符类型:
HTML特殊字符;
JavaScript特殊字符;
SQL特殊字符;
如果不对这些特殊字符进行转义处理,则不但可能破坏文档结构,还可以引发潜在的安全问题。Spring为HTML和JavaScript特殊字符提供了转义操作工具类,它们分别是HtmlUtils和JavaScriptUtils。
HTML特殊字符转义
HTML中<,>,&等字符有特殊含义,它们是HTML语言的保留字,因此不能直接使用。使用这些个字符时,应使用它们的转义序列:
&:&
":"
<:<
>:>
由于HTML网页本身就是一个文本型结构化文档,如果直接将这些包含了HTML特殊字符的内容输出到网页中,极有可能破坏整个HTML文档的结构。所以,一般情况下需要对动态数据进行转义处理,使用转义序列表示HTML特殊字符。下面的JSP网页将一些变量动态输出到HTML网页中:
清单1.未进行HTML特殊字符转义处理网页
<%@pagelanguage="java"contentType="text/html;charset=utf-8"%>
<%!
Stringusername="</td><tr></table>";
Stringaddress=""type="button";
%>
<tableborder="1">
<tr>
<td>姓名:</td><td><%=userName%></td>①
</tr>
<tr>
<td>年龄:</td><td>28</td>
</tr>
</table>
<inputvalue="<%=address%>" type="text"/>②
在①和②处,我们未经任何转义处理就直接将变量输出到HTML网页中,由于这些变量可能包含一些特殊的HTML的字符,它们将可能破坏整个HTML文档的结构。我们可以从以上JSP页面的一个具体输出中了解这一问题:
<tableborder="1">
<tr>
<td>姓名:</td><td></td><tr></table></td>
①破坏了<table>的结构
</tr>
<tr>
<td>年龄:</td><td>28</td>
</tr>
</table>
<inputvalue=""type="button" type="text"/>
②将本来是输入框组件偷梁换柱为按钮组件
融合动态数据后的HTML网页已经面目全非,首先①处的<table>结构被包含HTML特殊字符的userName变量截断了,造成其后的<table>代码变成无效的内容;其次,②处<input>被动态数据改换为按钮类型的组件(type="button")。为了避免这一问题,我们需要事先对可能破坏HTML文档结构的动态数据进行转义处理。Spring为我们提供了一个简单适用的HTML特殊字符转义工具类,它就是HtmlUtils。下面,我们通过一个简单的例子了解HtmlUtils的具体用法:
清单2.HtmpEscapeExample
packagecom.baobaotao.escape;
importorg.springframework.web.util.HtmlUtils;
publicclassHtmpEscapeExample{
publicstaticvoidmain(String[]args){
StringspecialStr="<divid="testDiv">test1;test2</div>";
Stringstr1=HtmlUtils.htmlEscape(specialStr);①转换为HTML转义字符表示
System.out.println(str1);
Stringstr2=HtmlUtils.htmlEscapeDecimal(specialStr);②转换为数据转义表示
System.out.println(str2);
Stringstr3=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提供了三个转义方法:
方法说明
staticStringhtmlEscape(Stringinput)将HTML特殊字符转义为HTML通用转义序列;
staticStringhtmlEscapeDecimal(Stringinput)将HTML特殊字符转义为带#的十进制数据转义序列;
staticStringhtmlEscapeHex(Stringinput)将HTML特殊字符转义为带#的十六进制数据转义序列;
此外,HtmlUtils还提供了一个能够将经过转义内容还原的方法:htmlUnescape(Stringinput),它可以还原以上三种转义序列的内容。运行以上代码,您将可以看到以下的输出:
str1:<divid="testDiv">test1;test2</div>
str2:<divid="testDiv">test1;test2</div>
str3:<divid="testDiv">test1;test2</div>
<divid="testDiv">test1;test2</div>
<divid="testDiv">test1;test2</div>
<divid="testDiv">test1;test2</div>
您只要使用HtmlUtils对代码清单1的userName和address进行转义处理,最终输出的HTML页面就不会遭受破坏了。
JavaScript特殊字符转义
JavaScript中也有一些需要特殊处理的字符,如果直接将它们嵌入JavaScript代码中,JavaScript程序结构将会遭受破坏,甚至被嵌入一些恶意的程序。下面列出了需要转义的特殊JavaScript字符:
‘:‘
":"
:
走纸换页:f
换行:n
换栏符:t
回车:r
回退符:b
?
我们通过一个具体例子演示动态变量是如何对JavaScript程序进行破坏的。假设我们有一个JavaScript数组变量,其元素值通过一个JavaList对象提供,下面是完成这一操作的JSP代码片断:
清单3.jsTest.jsp:未对JavaScript特殊字符进行处理
<%@pagelanguage="java"contentType="text/html;charset=utf-8"%>
<jsp:directive.pageimport="java.util.*"/>
<%
ListtextList=newArrayList();
textList.add("";alert();j="");
%>
<script>
vartxtList=newArray();
<%for(inti=0;i<textList.size();i++){%>
txtList[<%=i%>]="<%=textList.get(i)%>";
①未对可能包含特殊JavaScript字符的变量进行处理
<%}%>
</script>
当客户端调用这个JSP页面后,将得到以下的HTML输出页面:
<script>
vartxtList=newArray();
txtList[0]="";alert();j="";①本来是希望接受一个字符串,结果被植入了一段JavaScript代码
</script>
由于包含JavaScript特殊字符的Java变量直接合并到JavaScript代码中,我们本来期望①处所示部分是一个普通的字符串,但结果变成了一段JavaScript代码,网页将弹出一个alert窗口。想像一下如果粗体部分的字符串是“";while(true)alert();j="”时会产生什么后果呢?
因此,如果网页中的JavaScript代码需要通过拼接Java变量动态产生时,一般需要对变量的内容进行转义处理,可以通过Spring的JavaScriptUtils完成这件工作。下面,我们使用JavaScriptUtils对以上代码进行改造:
<%@pagelanguage="java"contentType="text/html;charset=utf-8"%>
<jsp:directive.pageimport="java.util.*"/>
<jsp:directive.pageimport="org.springframework.web.util.JavaScriptUtils"/>
<%
ListtextList=newArrayList();
textList.add("";alert();j="");
%>
<script>
vartxtList=newArray();
<%for(inti=0;i<textList.size();i++){%>
①在输出动态内容前事先进行转义处理
txtList[<%=i%>]="<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";
<%}%>
</script>
通过转义处理后,这个JSP页面输出的结果网页的JavaScript代码就不会产生问题了:
<script>
vartxtList=newArray();
txtList[0]="";alert();j="";
①粗体部分仅是一个普通的字符串,而非一段JavaScript的语句了
</script>
SQL特殊字符转义
应该说,您即使没有处理HTML或JavaScript的特殊字符,也不会带来灾难性的后果,但是如果不在动态构造SQL语句时对变量中特殊字符进行处理,将可能导致程序漏洞、数据盗取、数据破坏等严重的安全问题。网络中有大量讲解SQL注入的文章,感兴趣的读者可以搜索相关的资料深入研究。
虽然SQL注入的后果很严重,但是只要对动态构造的SQL语句的变量进行特殊字符转义处理,就可以避免这一问题的发生了。来看一个存在安全漏洞的经典例子:
SELECTCOUNT(userId)
FROMt_user
WHEREuserName=‘"+userName+"‘ANDpassword=‘"+password+"‘;
以上SQL语句根据返回的结果数判断用户提供的登录信息是否正确,如果userName变量不经过特殊字符转义处理就直接合并到SQL语句中,黑客就可以通过将userName设置为“1‘or‘1‘=‘1”绕过用户名/密码的检查直接进入系统了。
所以除非必要,一般建议通过PreparedStatement参数绑定的方式构造动态SQL语句,因为这种方式可以避免SQL注入的潜在安全问题。但是往往很难在应用中完全避免通过拼接字符串构造动态SQL语句的方式。为了防止他人使用特殊SQL字符破坏SQL的语句结构或植入恶意操作,必须在变量拼接到SQL语句之前对其中的特殊字符进行转义处理。Spring并没有提供相应的工具类,您可以通过jakartacommonslang通用类包中(spring/lib/jakarta-commons/commons-lang.jar)的StringEscapeUtils完成这一工作:
清单4.SqlEscapeExample
packagecom.baobaotao.escape;
importorg.apache.commons.lang.StringEscapeUtils;
publicclassSqlEscapeExample{
publicstaticvoidmain(String[]args){
Stringusername="1‘or‘1‘=‘1";
Stringpassword="123456";
userName=StringEscapeUtils.escapeSql(userName);
password=StringEscapeUtils.escapeSql(password);
Stringsql="SELECTCOUNT(userId)FROMt_userWHEREuserName=‘"
+userName+"‘ANDpassword=‘"+password+"‘";
System.out.println(sql);
}
}
事实上,StringEscapeUtils不但提供了SQL特殊字符转义处理的功能,还提供了HTML、XML、JavaScript、Java特殊字符的转义和还原的方法。如果您不介意引入jakartacommonslang类包,我们更推荐您使用StringEscapeUtils工具类完成特殊字符转义处理的工作。
方法入参检测工具类
Web应用在接受表单提交的数据后都需要对其进行合法性检查,如果表单数据不合法,请求将被驳回。类似的,当我们在编写类的方法时,也常常需要对方法入参进行合法性检查,如果入参不符合要求,方法将通过抛出异常的方式拒绝后续处理。举一个例子:有一个根据文件名获取输入流的方法:InputStreamgetData(Stringfile),为了使方法能够成功执行,必须保证file入参不能为null或空白字符,否则根本无须进行后继的处理。这时方法的编写者通常会在方法体的最前面编写一段对入参进行检测的代码,如下所示:
publicInputStreamgetData(Stringfile){
if(file==null||file.length()==0||file.replaceAll("s","").length()==0){
thrownewIllegalArgumentException("file入参不是有效的文件地址");
}
…
}
类似以上检测方法入参的代码是非常常见,但是在每个方法中都使用手工编写检测逻辑的方式并不是一个好主意。阅读Spring源码,您会发现Spring采用一个org.springframework.util.Assert通用类完成这一任务。
Assert翻译为中文为“断言”,使用过JUnit的读者都熟知这个概念,它断定某一个实际的运行值和预期想一样,否则就抛出异常。Spring对方法入参的检测借用了这个概念,其提供的Assert类拥有众多按规则对方法入参进行断言的方法,可以满足大部分方法入参检测的要求。这些断言方法在入参不满足要求时就会抛出IllegalArgumentException。下面,我们来认识一下Assert类中的常用断言方法:
断言方法说明
notNull(Objectobject)当object不为null时抛出异常,notNull(Objectobject,Stringmessage)方法允许您通过message定制异常信息。和notNull()方法断言规则相反的方法是isNull(Objectobject)/isNull(Objectobject,Stringmessage),它要求入参一定是null;
isTrue(booleanexpression)/isTrue(booleanexpression,Stringmessage)当expression不为true抛出异常;
notEmpty(Collectioncollection)/notEmpty(Collectioncollection,Stringmessage)当集合未包含元素时抛出异常。notEmpty(Mapmap)/notEmpty(Mapmap,Stringmessage)和notEmpty(Object[]array,Stringmessage)/notEmpty(Object[]array,Stringmessage)分别对Map和Object[]类型的入参进行判断;
hasLength(Stringtext)/hasLength(Stringtext,Stringmessage)当text为null或长度为0时抛出异常;
hasText(Stringtext)/hasText(Stringtext,Stringmessage)text不能为null且必须至少包含一个非空格的字符,否则抛出异常;
isInstanceOf(Classclazz,Objectobj)/isInstanceOf(Classtype,Objectobj,Stringmessage)如果obj不能被正确造型为clazz指定的类将抛出异常;
isAssignable(ClasssuperType,ClasssubType)/isAssignable(ClasssuperType,ClasssubType,Stringmessage)subType必须可以按类型匹配于superType,否则将抛出异常;
使用Assert断言类可以简化方法入参检测的代码,如InputStreamgetData(Stringfile)在应用Assert断言类后,其代码可以简化为以下的形式:
publicInputStreamgetData(Stringfile){
Assert.hasText(file,"file入参不是有效的文件地址");
①使用Spring断言类进行方法入参检测
…
}
可见使用Spring的Assert替代自编码实现的入参检测逻辑后,方法的简洁性得到了不少的提高。Assert不依赖于Spring容器,您可以大胆地在自己的应用中使用这个工具类。
小结
本文介绍了一些常用的Spring工具类,其中大部分Spring工具类不但可以在基于Spring的应用中使用,还可以在其它的应用中使用。
对于Web应用来说,由于有很多关联的脚本代码,如果这些代码通过拼接字符串的方式动态产生,就需要对动态内容中特殊的字符进行转义处理,否则就有可能产生意想不到的后果。Spring为此提供了HtmlUtils和JavaScriptUtils工具类,只要将动态内容在拼接之前使用工具类进行转义处理,就可以避免类似问题的发生了。如果您不介意引入一个第三方类包,那么jakartacommonslang通用类包中的StringEscapeUtils工具类可能更加适合,因为它提供了更加全面的转义功能。
最后我们还介绍了Spring的Assert工具类,Assert工具类是通用性很强的工具类,它使用面向对象的方式解决方法入参检测的问题,您可以在自己的应用中使用Assert对方法入参进行检查。
原文地址:http://www.cnblogs.com/tankaixiong/articles/2470243.html