Spring中html字符编解码
场景描述
今天基于 jeesite 在做一个简单的curd的功能时,遇到个比较有意思的问题,在此记录一下,在使用springMVC form标签作双向绑定时,如果文本域中填写的为html代码,提交到后端会自动进行转义,如下:
1
| <form:textarea path="content" id="content" style="width:600px;height:300px;"/> |
提交后,在后端接收到的content自动会将<,> &等符号自动转义成 \<,\>,\&等字符,导致落地到数据库时的内容已经是转义后的内容了。
原因分析
一开始比较疑惑,为什么会转义呢? 在Controller中看到的对象中的属性绑定的值确实是已经转义的。检查代码时,没有发现有手动转义的地方,在检查父级代码时,至看到BaseController此方法,才得以解惑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /** * 初始化数据绑定 * 1. 将所有传递进来的String进行HTML编码,防止XSS攻击 * 2. 将字段中Date类型转换为String类型 */ @InitBinder protected void initBinder(WebDataBinder binder) { // String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击 binder.registerCustomEditor(String.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim())); } @Override public String getAsText() { Object value = getValue(); return value != null ? value.toString() : ""; } }); // Date 类型转换 binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtils.parseDate(text)); } }); } |
我们来看看:
1
| StringEscapeUtils.escapeHtml4() |
该方法是org.apache.commons.lang3包,主要是用于html转义,那么,既然在这里转义了,我们保存在数据库时,可以进行恢复成转义前的状态即可解决该问题。
注意: *(作者的业务要求是需要使用标准的html内容,如果你的业务需要将该html内容显示到前端,建议直接保存转义后的内容)
处理办法
找到原因后,我们需要将已经转义的html代码,还原回来,依旧可以使用StringEscapeUtils类中提供的方法
1
| StringEscapeUtils.unescapeHtml4(html); |
可以使用上述方法进行恢复。
注意: 在新版的common-lang.jar中,该方法已经为过时。个人建议切勿过度依赖,或者替换成spring中的HtmlUtils方法。
HtmlUtils
在spring中找到一个功能相同的工具类,HtmlUtils,也提供十进制,十六进制的转义方法,使用方法如下所示:
在HtmlUtils类中,一共提供了4类方法:
1.转换为十进制
1 2 3 4 | public void testEsapeDecimal(){ String result = HtmlUtils.htmlEscapeDecimal("\"<html lang=\\\"en\\\"><head><title>标题</title></head><body>内容</body></html>\";"); System.out.println(result); } |
转换结果:
1
| "<html lang=\"en\"><head><title>标题</title></head><body>内容</body></html>" |
- 转换为十六进制
1 2 3 4 5
@Test public void testEsacpeHex(){ String result = HtmlUtils.htmlEscapeHex("\"<html lang=\\\"en\\\"><head><title>标题</title></head><body>内容</body></html>\";"); System.out.println(result); }
转换结果:
1
| "<html lang=\"en\"><head><title>标题</title></head><body>内容</body></html>";
|
- 转义
1 2 3 4 5
@Test public void testEscape(){ String result = HtmlUtils.htmlEscape("<html lang=\"en\"><head><title>标题</title></head><body>内容</body></html>"); System.out.println(result); }
转换为:
1
| <html lang="en"><head><title>标题</title></head><body>内容</body></html>
|
- 解码,该方法参数无论是十进制,十六进制,转义后的code,均可以用该方法进行解码
1 2 3 4 5 6 7 8
/** * 解码 * @Test public void testUnEscape(){ String result = HtmlUtils.htmlUnescape("<html lang="en"><head><title>标题</title></head><body>内容</body></html>"); System.out.println(result); }
解码后:
1
| <html lang="en"><head><title>标题</title></head><body>内容</body></html> |
常见编码
以下为常见符号的转换表,包括符号,十进制,十六进制,转义字符的转换:
符号|十进制|十六进 |转义字符
—|—|—|—|
“|\"|\"|\"|
>|\>|\>|\>|
<|\<|\<|\<|
&|\&|\&|\&|
‘|\'|\'|\'|
由于对表格支持不太友好,效果如下图所示:
小结:
现在想想该问题,我们在日常的编码过程中,不仅仅是以功能实现为标准,更要注重该功能的安全性,像评论区,输入框在使用过程中,应该考虑到将特殊字符转义。编码时的多思考就能够把问题在萌芽阶段就处理掉。
参考链接:
https://stackoverflow.com/questions/1265282/recommended-method-for-escaping-html-in-java
https://docs.spring.io/spring/docs/4.2.9.RELEASE/spring-framework-reference/
疑惑:
查看spring文档时,文档中详细的说明了在使用spring form标签时,有提供不同的方法来指定页面,表单,甚至输入框是否需要转义,
1 2 3 4 5 6 7 8 9 10 11 | //1. 设置单个输入框的,(表单输入框中) <form:input path="username" htmlEscape="true"/> //2. 设置页面是否转义,会覆盖web.xml文件中配置的,(jsp页面中) <spring:htmlEscape defaultHtmlEscape="true" /> //3. 设置全局的,(web.xml中) <context-param> <param-name>defaultHtmlEscape</param-name> <param-value>true</param-value> </context-param> |
配置好后,在controller中接收到的html内容依旧没有被转义。也就是说,该属性设置与不设置的效果是一样的。一直没有找到原因是什么. 持续疑惑中….. 如果有知道问题的小伙伴,烦请告知原因。