Spring Boot AntiSamy XSS多层防御从过滤器到反序列化
[024][Web模块]基于 AntiSamy 的 Spring Boot XSS 防护实践:从过滤器到反序列化的多层防御
跨站脚本攻击(XSS)依然是 Web 应用中最常见的安全威胁之一。本文从一个真实的企业级项目出发,详细拆解一套完整的 XSS 防护方案。方案的核心是 OWASP 的 AntiSamy 项目,用它作为清洗引擎,再通过 Servlet 过滤器和 Jackson 反序列化器这两层机制,分别拦截请求参数和 JSON 正文里的恶意代码。这样一来,用户输入的方方面面都能被覆盖到。下文会逐一剖析 XssHttpServletFilter、XssJsonDeserializer、XssSimpleModule 和 XssUtils 这四个核心组件的设计与实现,最后给出在 Spring Boot 里集成的示例。
1. 引言
XSS 攻击的本质其实很简单:攻击者把恶意脚本注入到一个看似可信的网站里,当其他用户访问时,脚本就在他们的浏览器中执行。传统的防御手段有很多,输出转义、输入校验、内容安全策略(CSP)等等。其中,输入清洗——也就是过滤掉 HTML 和 JS 里的危险内容——是一种覆盖面广且效果不错的防御手段。
本文介绍的方案基于 OWASP 的 AntiSamy。这是一个专门用来过滤用户提交的 HTML 和 CSS 内容的 Java 库。开发者只需提供一个策略文件(XML),就能精细控制哪些标签、属性、CSS 属性是允许的。和简单的正则替换相比,AntiSamy 能正确解析 HTML 结构,避免被绕过。
2. 整体架构
下图展示了 XSS 防护的整体数据流向:
HTTP Request (GET/POST) │ ▼┌──────────────────────┐│ XssHttpServletFilter │→ 包装 HttpServletRequest└──────────────────────┘ │ ▼ (表单参数、Query参数、请求头)┌──────────────────────────────┐│ XssHttpServletRequestWrapper │→ 重写 getParameter 等方法└──────────────────────────────┘ │ ▼ XssUtils.cleaning()← AntiSamy 清洗 │ ▼Controller 接收到的参数已清洗────────────────────────────HTTP Request (JSON Body) │ ▼┌─────────────────────┐│ XssSimpleModule │→ 注册到 Jackson ObjectMapper└─────────────────────┘ │ ▼┌─────────────────────────┐│ XssJsonDeserializer │→ 反序列化 String 字段时调用└─────────────────────────┘ │ ▼ XssUtils.cleaning() │ ▼@RequestBody 对象中的字符串已清洗
关键设计思想
- 非侵入式:过滤器和 Jackson 模块都通过配置加入,业务代码不需要任何改动。
- 分层防御:同时覆盖
application/x-www-form-urlencoded、multipart/form-data、query string和application/json这四种常见请求格式。 - 可配置策略:通过修改 AntiSamy 的策略文件,可以灵活控制允许的标签,比如保留
、,但删除。
3. 核心组件详解
3.1 XssUtils – 底层清洗引擎
XssUtils 是整个防 XSS 方案的核心工具类,它封装了 AntiSamy 的调用逻辑。采用单例模式,确保策略文件只加载一次。
private XssUtils() { Policy policy = createPolicy();this.antiSamy = ObjectUtils.isNotEmpty(policy) ? new AntiSamy(policy) : new AntiSamy();this.nbsp = cleanHtml(" ");this.quot = cleanHtml(""");}
关键特性:
- 策略加载:从 classpath
antisamy/antisamy-anythinggoes.xml读取策略文件。如果加载失败(比如文件缺失),就回退到 AntiSamy 的默认策略。 - 字符乱码修复:AntiSamy 在处理
和双引号时,可能会产生异常字符(这和策略以及 JDK 版本有关)。代码会预先清洗这两个字符串,然后在最终结果中用replaceAll将其还原或清除。 - 实体反转义:调用
StringEscapeUtils.unescapeHtml4()对输入先进行反转义,避免 AntiSamy 二次转义导致误判。
清洗流程(cleaning 方法):
- 反转义 HTML 实体(比如
<→<)。 - 调用
cleanHtml→antiSamy.scan()。 - 替换
产生的乱码为空字符串。 - 替换双引号乱码为原始双引号。
- 移除所有换行符(可以根据需要调整)。
- 返回清洗后的字符串。
3.2 XssHttpServletFilter – 请求参数清洗
这个过滤器实现了 javax.servlet.Filter,作用是把原始的 HttpServletRequest 包装成自定义的 XssHttpServletRequestWrapper。
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) { HttpServletRequest request = (HttpServletRequest) servletRequest;XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(request);filterChain.doFilter(xssRequest, servletResponse);}
XssHttpServletRequestWrapper 会重写以下方法:
getParameter(String name)getParameterValues(String name)getParameterMap()getHeader(String name)
在这些方法里,调用 XssUtils.cleaning() 对每个字符串值进行清洗,然后返回安全版本。这样一来,@RequestParam、@ModelAttribute,甚至原生 request.getParameter() 拿到的数据都是已经过滤过的。
优点很明显:一次配置,所有 GET/POST 表单参数自动受到保护。缺点是可能会误伤正常的 HTML 提交(比如富文本编辑器里的内容),这就需要根据业务场景调整策略文件,或者提供绕过方式。
3.3 XssJsonDeserializer – JSON 字符串清洗
当请求体是 JSON 时,Spring 默认用 Jackson 进行反序列化。XssJsonDeserializer 继承自 JsonDeserializer,并注册到 SimpleModule 里。
public class XssJsonDeserializer extends JsonDeserializer
所有被 Jackson 反序列化为 String 的 JSON 字段都会触发这个反序列化器,从而执行 XssUtils.cleaning()。
3.4 XssSimpleModule – Jackson 模块注册
XssSimpleModule 是 SimpleModule 的子类,在构造器里绑定 String.class 到 XssJsonDeserializer。
public XssSimpleModule() { super(XssSimpleModule.class.getName(), JsonConsts.JSON_VERSION);this.addDeserializer(String.class, XssJsonDeserializer.instance);}
应用启动时,只要把这个模块添加到 ObjectMapper 里,就能全局生效。
4. 在 Spring Boot 中集成
4.1 添加依赖
4.2 注册 Filter
@Configurationpublic class XssFilterConfig { @Beanpublic FilterRegistrationBean
4.3 注册 Jackson Module
@Configurationpublic class XssJacksonConfig { @Beanpublic ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.build();mapper.registerModule(new XssSimpleModule());return mapper;}}
4.4 策略文件放置
将 antisamy-anythinggoes.xml 放到 src/main/resources/antisamy/ 目录下。这个策略文件可以从 OWASP 官方获取,也可以自定义。
5. 测试样例
5.1 表单/URL 参数测试
Controller:
@PostMapping("/comment")public String addComment(@RequestParam String content) { return "Your comment: " content;}
请求:
POST /comment?content=Hello
响应:
Your comment: Hello
标签被策略删除。
5.2 JSON 请求体测试
Controller:
@PostMapping("/comment/json")public Comment addCommentJson(@RequestBody Comment comment) { return comment;}public class Comment { private String author;private String text;// getters/setters}
请求:
{ "author": "John","text": "Safe

