Spring中html字符编解码

在Spring MVC中,当表单提交HTML代码时,Spring会自动将其转义。文章介绍了这一行为的原因,以及如何通过StringEscapeUtils或HtmlUtils进行解码,以保存原始的HTML内容到数据库。此外,还分享了常见HTML转义字符的转换表,并讨论了在处理用户输入时的安全考虑。尽管尝试了Spring的表单转义配置,但未能阻止自动转义,作者对此感到疑惑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
Spring IOC 控制反转:把创建对象的权利交给Spring 创建对象 1.无参构造<bean class=""> 2.静态工厂<bean class="" factory-method=""> 3.实例工厂 <bean bean-factory="" factory-method=""> 管理对象 对象关系DI 构造器注入<construct-arg> set注入<property> 生命周期 scope:prototype/singleton init-method destroy-method API BeanFactory:使用这个工厂创建对象的方式都是懒加载,在调用的时候再创建 ClassPathXmlApplicationContext:使用这个工厂创建对象,他会根据scope智能判断是否懒加载,如果是单例则创建容器时就会创建里面bean的实例,如果是多例在获取使用时才会创建bean实例 FileSystemXmlApplicationContext磁盘路径 AnnotationConfigApplicationContext注解 WebApplicationContext:web环境使用的容器 注解 创建对象 Component:不分层的注解 Controller:web层 Service:service层 Repository:dao层 管理对象 注入 AutoWired Qualifier Resource Value 声明周期 Scope PostConstruct PreDestroy 新注解 Bean:写方法上,将方法的返回值 Configuration:标记配置类 ComponentScan包扫描 PropertySource:加载配置文件 Import:导入其他配置类 AOP 概念:面向切面编程,在不改变源码的情况下对方法进行增强,抽取横切关注点(日志处理,事务管理,安全检查,性能测试等等),使用AOP进行增强,使程序员只需要关注与业务逻辑编写. 专业术语 目标Target:需要增强的类 连接点JoinPoint:目标中可被增强的方法 切入点PointCut:被增强的方法 增强Advice:增强代码 切面Aspect:切点加通知 织入weaving:讲切面加载进内存形成代理对象的过程 代理Proxy 底层实现 JDK动态代理(默认) 基于接口:代理对象与目标对象是兄弟关系,目标类必须实现接口 CGLIB动态代理 基于父类:代理对象与目标对象是父子关系.目标不能被final修饰 修改默认代理方法:<aop:aspectj-autoproxy proxy-target-class="true"/> 增强种类 前置通知 后置通知 异常通知 最终通知 环绕通知 注意:使用注解的方式,最终通知和后置通知顺序换了,建议使用环绕通知 注解 配置 声明式事务管理 PlatFormTransactionManager:平台事务管理器:定义了commit/rollback Mybatis/jdbc:DataSourceTransactionManager Hibernater:HibernaterTransactionManager TransactionManagerDifinition 传播行为:A-->B,在B上声明是否一定需要事务管理 requerd:必须的(默认),如果A有事务那么就加入A的事务,如果A没有事务那么单独创建一个事务 supports,如果A有事务则加入,如果没有就算了 隔离级别 default:使用数据库默认的隔离级别(mysql:可重复读,oracle:读已提交) readuncommited:读未提交,不可以解决任何问题 readcommited:读已提交,可以解决脏读问题 repeatableRead:可重复读,可以解决脏读,不可重复读问题 Serializbler:串行化,可以解决所有问题 超时时间: 默认-1(永不超时),事务一直不提交也不回滚的时间 是否只读: 默认false TransactionManagerStatus: 事务的一些状态 整合 Spring整合Junit 1.导入依赖spring-test 2.加注解:RunWith、ContextConfiguration 3.注入对象进行测试 Spring整合web 1.导入依赖spring-web 2.配置ContextLoadListener 3.配置 <!--全局初始化参数--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> 4.在Servlet中使用WebApplicationContextUtils获取容器对象 5.使用容器对象去获取Service对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值