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
4public 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内容依旧没有被转义。也就是说,该属性设置与不设置的效果是一样的。一直没有找到原因是什么. 持续疑惑中….. 如果有知道问题的小伙伴,烦请告知原因。