由于 Web 应用程序需要联合使用到多种语言,每种语言都包含一些特殊的字符,对于动态语言或标签式的语言而言,如果需要动态构造语言的内容时,一个我们经常会碰到的问题就是特殊字符转义的问题。下面是 Web 开发者最常面对需要转义的特殊字符类型:

  • HTML 特殊字符;
  • JavaScript 特殊字符;
  • SQL 特殊字符;

如果不对这些特殊字符进行转义处理,则不但可能破坏文档结构,还可以引发潜在的安全问题。Spring 为 HTML 和 JavaScript 特殊字符提供了转义操作工具类,它们分别是 HtmlUtils 和 JavaScriptUtils。

个人补充 : SQL可以采用 prepareStatement;

HTML 特殊字符转义

HTML 中 <,>,& 等字符有特殊含义,它们是 HTML 语言的保留字,因此不能直接使用。使用这些个字符时,应使用它们的转义序列:

  • &:&
  • " :"
  • < :<
  • :>

由于 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 网页已经面目全非,首先 ① 处的

结构被包含 HTML 特殊字符的 userName 变量截断了,造成其后的 代码变成无效的内容;其次,② 处 被动态数据改换为按钮类型的组件(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 提供了三个转义方法:

  1. static String htmlEscape(String input) —> 将 HTML 特殊字符转义为 HTML通用转义序列;
  2. static String htmlEscapeDecimal(String input) —> 将 HTML 特殊字符转义为带# 的十进制数据转义序列;
  3. static String htmlEscapeHex(String input) —> 将 HTML 特殊字符转义为带 #的十六进制数据转义序列;

此外,HtmlUtils 还提供了一个能够将经过转义内容还原的方法:htmlUnescape(String input),它可以还原以上三种转义序列的内容。运行以上代码,您将可以看到以下的输出:

str1:&lt;div id=&quot;testDiv&quot;&gt;test1;test2&lt;/div&gt;
str2:<div id="testDiv">test1;test2</div>
str3:<div id="testDiv">test1;test2</div>
<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

===========
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 类中的常用断言方法:

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 类中的常用断言方法:

  1. notNull(Object object) —> 当 object 不为 null 时抛出异常,notNull(Object object, String message) 方法允许您通过 message 定制异常信息。和 notNull() 方法断言规则相反的方法是 isNull(Object object)/isNull(Object object, String message),它要求入参一定是 null;
  2. isTrue(boolean expression) / isTrue(boolean expression, String
    message) —> 当 expression 不为 true 抛出异常;
  3. 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[] 类型的入参进行判断;
  4. hasLength(String text) / hasLength(String text, String message) —> 当 text 为 null 或长度为 0 时抛出异常;
  5. hasText(String text) / hasText(String text, String message) —> text 不能为 null 且必须至少包含一个非空格的字符,否则抛出异常;
  6. isInstanceOf(Class clazz, Object obj) / isInstanceOf(Class type, Object obj, String message) --> 如果 obj 不能被正确造型为 clazz 指定的类将抛出异常;
  7. 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 对方法入参进行检查。

原文转自 : https://www.ibm.com/developerworks/cn/java/j-lo-spring-utils2/index.html

spring特殊字符转义和方法入参检测工具类相关推荐

  1. 2021 Spring 自定义注解 +AOP +方法入参

    不同于用JoinPoint获取方法的参数值,这种直接注入参数的方式简洁又方便 一:aop 实现类 @Aspect @Component public class CacheDelByRegexAop{ ...

  2. aspect 方法入参 获取_谈谈Spring AOP中@Aspect的高级用法示例

    前言 本文主要跟大家分享介绍了关于Spring AOP中@Aspect的高级用法,下面话不多说了,来随着小编一起看看详细的介绍吧. 1 切点复合运算 支持在切点定义中加入以下运算符进行复合运算: 运算 ...

  3. Spring-AOP @AspectJ进阶之绑定连接点方法入参

    文章目录 概述 实例 概述 我们前面的博文在讲解切点函数时说过args().this().target().@args().@within().@target()和@annotation()这7个函数 ...

  4. aspectj 获取方法入参_深入探索编译插桩技术(二、AspectJ)

    本文来自jsonchao的投稿,个人微信:bcce5360 现如今,编译插桩技术已经深入 Android 开发中的各个领域,而 AOP 技术正是一种高效实现插桩的模式,它的出现正好给处于黑暗中的我们带 ...

  5. aop 获取方法入参出参_ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称及入参和出参结果并记录方法耗时...

    作者:小傅哥 博客:bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获! ❞ 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了 ...

  6. 方法入参很复杂,每次调用都要构造BO入参?一招教你自动构造入参

    场景 同在互联网打工的小伙伴们肯定都面临这样一种场景: 通用逻辑(被多处调用)我们通常会封装成一个方法,那这个方法入参正常来说都不会少,(在开发规范中,经常会看到一条"方法入参正常不超过3个 ...

  7. 新版SpringCloudGateway网关 切面修改方法入参

    通过注解修改方法入参值,一般都是采用实现 org.springframework.web.method.support.HandlerMethodArgumentResolver 接口的 resolv ...

  8. Mybatis方法入参处理

    1,在单个入参的情况下,mybatis不做任何处理,#{参数名} 即可,甚至连参数名都可以不需要,因为只有一个参数,或者使用 Mybatis的内置参数 _parameter. 2,多个入参: 接口方法 ...

  9. Spring注入service为null另类解决办法 工具类 一般类 静态 非controller

    系统为SpringMVC框架,在开发的过程中有一些工具类需要调用下由spring管理的service层.但是一进注入不进来,报null异常: 在尝试了网上的一系列方法后,还是没有解决.网上的解决方法主 ...

最新文章

  1. Delphi7 (第二天:结构及常用函数)
  2. Matlab画地球剖面图,分享用matlab显示地震记录的波形变面积图
  3. 转:在MyEclipse下创建Java Web项目 入门(图文并茂)经典教程
  4. (绝对正确)Zabbix基于lamp的安装流程
  5. 分子克隆基础:什么是质粒
  6. Java虚拟机:JVM 主要组成部分与内存区域
  7. 名词解释(容器、并发,插件,脚本)及程序对象的创建和注释文档
  8. xampp mysql是空的_xampp中修改mysql默认空密码(root密码)的方法分享
  9. vmware虚拟机里的服务器自动关闭,让VirtualBox虚拟机在主机关闭时自动关闭或保存状态VBoxVmService...
  10. Hadoop集成环境搭建
  11. Mac盖上屏幕后外接屏幕持续黑画面的解决方法
  12. 成功解决不能完成“视频帧到图层”的命令,因为需要QuickTime7.1或者更高版本
  13. 如何撰写总体设计与详细设计文档
  14. 基础物理-物质的组成
  15. 循环结构:while和do...while循环语句
  16. 程序化交易入门(一)
  17. 全国大学生电子设计竞赛(三)--线性电源设计
  18. 《英语语法新思维初级教程》学习笔记(四)数量限定词和个体限定词
  19. 解决Sonar File won‘t be refreshed because there were errors during analysis
  20. 【尊享版】如何从零到一掌控习惯?

热门文章

  1. Go获取n天前或后的日期、日期转秒时间戳、计算两个日期之间的天数差
  2. C++vector应用
  3. 来自山西机器人乐队_清华机器人乐队“墨甲”诞生了:中国风浓郁,还演出了舞台剧...
  4. 大学生python心得1000字_大学生社会实践报告心得体会范文1000字(精选4篇)
  5. 明基逐鹿,SaaS领域的HR奇兵
  6. 豆瓣电影,电视剧DM实战
  7. CANoe软件使用(四)——CANoe与Simulink联合仿真
  8. Android面试复习资料整理
  9. 【ruby】ruby语言的几个特性总结
  10. 多目标跟踪MOT技术总结(持续更新)