Spring中html字符编解码

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
&#34;&#60;html lang=\&#34;en\&#34;&#62;&#60;head&#62;&#60;title&#62;标题&#60;/title&#62;&#60;/head&#62;&#60;body&#62;内容&#60;/body&#62;&#60;/html&#62;&#34;

  1. 转换为十六进制
    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
&#x22;&#x3c;html lang=\&#x22;en\&#x22;&#x3e;&#x3c;head&#x3e;&#x3c;title&#x3e;标题&#x3c;/title&#x3e;&#x3c;/head&#x3e;&#x3c;body&#x3e;内容&#x3c;/body&#x3e;&#x3c;/html&#x3e;&#x22;;

  1. 转义
    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
&lt;html lang=&quot;en&quot;&gt;&lt;head&gt;&lt;title&gt;标题&lt;/title&gt;&lt;/head&gt;&lt;body&gt;内容&lt;/body&gt;&lt;/html&gt;

  1. 解码,该方法参数无论是十进制,十六进制,转义后的code,均可以用该方法进行解码
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 解码
    *
    @Test
    public void testUnEscape(){
    String result = HtmlUtils.htmlUnescape("&#60;html lang=&#34;en&#34;&#62;&#60;head&#62;&#60;title&#62;标题&#60;/title&#62;&#60;/head&#62;&#60;body&#62;内容&#60;/body&#62;&#60;/html&#62;");
    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内容依旧没有被转义。也就是说,该属性设置与不设置的效果是一样的。一直没有找到原因是什么. 持续疑惑中….. 如果有知道问题的小伙伴,烦请告知原因。


这里写图片描述

扫码关注,一起进步

个人博客: http://www.andyqian.com