
跨站脚本攻击(XSS)仍是当今Web应用面临的最常见安全威胁之一。攻击者通过注入恶意脚本,可以窃取用户Cookie、会话令牌甚至执行任意操作。对于Java Web应用,常见的防御手段包括对输入输出进行HTML转义、使用内容安全策略(CSP)或引入专门的过滤库。
本文介绍一套基于 OWASP AntiSamy 实现的XSS防护过滤器,它能够对HTTP请求参数、请求头进行深度清洗,移除不符合策略的HTML标签、属性和脚本。整套代码分为三个核心类,分别为 XssHttpServletFilter、XssHttpServletRequestWrapper 和 XssUtils,结构清晰、可插拔,适合集成到任何基于Servlet规范的Java Web应用中。
类名 | 类型 | 职责 |
|---|---|---|
|
| 拦截请求,将原始 |
|
| 重写 |
| 工具类(单例) | 封装 OWASP AntiSamy,加载策略文件,执行清洗并处理特殊字符乱码。 |
过滤器非常简单,仅做一件事:将原始 ServletRequest 转型为 HttpServletRequest,然后创建 XssHttpServletRequestWrapper 实例,并将包装后的对象传递给过滤链。
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(request);
filterChain.doFilter(xssRequest, servletResponse);注意:过滤器本身不执行任何清洗逻辑,所有清洗工作都委托给包装器。
该包装器继承 HttpServletRequestWrapper,利用装饰器模式,对需要拦截的方法进行增强:
getHeader(String name):清洗单个请求头值。getParameter(String name):清洗单个请求参数。getParameterValues(String name):清洗同名参数的字符串数组。getParameterMap():清洗整个参数映射表,每个值数组都会被递归清洗。所有清洗最终调用 XssUtils.cleaning()。对于空值或空白字符串,直接返回原值,避免无谓的清洗开销。
XssUtils 采用双重检查锁实现线程安全的单例。构造时会从 classpath 加载策略文件 antisamy/antisamy-anythinggoes.xml:
URL url = ResourceUtils.getURL("classpath:antisamy/antisamy-anythinggoes.xml");
return Policy.getInstance(url);若加载失败(例如文件缺失),则回退到 AntiSamy 默认策略(较为严格)。
cleaning(String taintedHTML))StringEscapeUtils.unescapeHtml4() 对输入进行反转义。这一步非常关键:AntiSamy 内部在扫描时会再次对特殊字符进行转义,如果不提前反转义,会导致重复转义,例如 < 最终变成 &lt;。CleanResults 并提取清洗后的 HTML。 处理成不可读的乱码,也会把双引号转换成 "。代码通过构造时预先清洗 " " 和 "\"" 得到对应的乱码字符串(nbsp 和 quot 成员变量),然后用 replaceAll 将它们分别还原为空字符串和原始双引号。\n),使输出更紧凑(这一行为是可选的,可根据需求调整)。若 scan() 抛出 ScanException 或 PolicyException,则直接返回原始字符串,并记录调试日志,保证服务不中断。
下图展示了从请求进入过滤器到返回清洗后数据的完整流程:
[HTTP 请求]
→ XssHttpServletFilter
→ 包装为 XssHttpServletRequestWrapper
→ 业务代码调用 request.getParameter("comment")
→ XssHttpServletRequestWrapper.getParameter()
→ XssUtils.cleaning(原始输入)
├─ unescapeHtml4
├─ antiSamy.scan
└─ 修复 / 双引号 / 换行符
→ 返回清洗后的字符串
→ 业务代码使用安全数据需要引入以下核心依赖(Maven 示例):
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.11.0</version>
</dependency>
<!-- Spring 工具包(用于加载 classpath 资源) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.0.0</version>
</dependency>在 src/main/resources/antisamy/ 目录下放置 antisamy-anythinggoes.xml(可从此处下载:OWASP AntiSamy 策略文件)。你也可以根据业务需求自定义策略,例如允许某些富文本标签(<b>、<i>、<img>)但禁止 onload 等事件属性。
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>tutorials4j.framework.web.mvc.filter.XssHttpServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<XssHttpServletFilter> xssFilterRegistration() {
FilterRegistrationBean<XssHttpServletFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new XssHttpServletFilter());
registration.addUrlPatterns("/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}getParameterValues 返回 String[],代码使用 Arrays.stream().map(XssUtils::cleaning).collect(Collectors.toList()) 对每个元素单独清洗,保证了多选框、多值参数的全面防护。
重写 getParameterMap() 时,直接对 Map<String, String[]> 的每个 value 调用 cleaning(String[]),确保无论业务代码通过哪种方式获取参数,都能得到清洗后的数据。
常见的XSS攻击也可能出现在 User-Agent、Referer 等头字段,因此 getHeader 也进行了同样的清洗。
在 XssUtils 构造器中,分别用 cleanHtml(" ") 和 cleanHtml("\"") 获取 AntiSamy 处理后的结果。例如 cleanHtml("\"") 可能返回 " 或者乱码 �,后续 cleaning 方法中会将这些特定乱码替换回预期字符。这个技巧有效解决了不同 AntiSamy 版本带来的编码不一致问题。
application/json 格式的请求体(例如 RESTful API)。如需支持 JSON,需要额外扩展(例如在 HttpServletRequest.getInputStream 中进行处理)。antisamy-anythinggoes.xml 存在于 classpath 中,否则回退到默认策略可能导致业务异常。getReader() 和 getInputStream(),读取 Body 后使用 Jackson 解析并递归清洗字符串字段。XssUtils.cleaning() 中当检测到输入被修改时,记录告警日志,便于安全审计。本文提供的 XSS 防护过滤器利用 OWASP AntiSamy 的强大策略引擎,以 Servlet 过滤器 + 请求包装器的典型模式,为 Java Web 应用加装了一层可靠的“免疫系统”。通过短短几个类,实现了对请求参数和头信息的全量清洗,且不影响原有业务逻辑。虽然存在一定的性能开销和对富文本场景的局限性,但对于大多数传统表单提交、评论、搜索等非富文本输入场景,是一个非常实用的安全加固方案。
如果你的项目正在寻找一种轻量、标准化且可细粒度控制的XSS防御手段,不妨尝试集成这套过滤器。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。