1. 项目概述为什么我们需要一个“清洁工”在Web应用开发的世界里数据就像从四面八方涌来的水流用户输入、第三方接口、甚至爬虫脚本都可能携带泥沙。XSS跨站脚本攻击就是其中最隐蔽、最危险的“泥沙”之一。它不像SQL注入那样直接攻击数据库而是潜伏在返回给用户的页面中窃取Cookie、劫持会话、甚至冒充用户执行操作防不胜防。很多开发者尤其是刚接触SpringBoot的朋友可能会觉得框架本身已经足够安全或者依赖前端进行转义就够了。但现实是安全防线必须层层设卡后端作为数据的最终处理者和存储者对输入进行净化和校验是不可或缺的最后一道闸门。这个项目的核心就是为你的SpringBoot应用配置一个高效、灵活的“数据清洁工”——基于Hutool工具包的XSS过滤器。它不只是一个简单的字符替换而是一套完整的防御策略。通过这个过滤器所有进入你应用的HTTP请求参数、请求体都会经过一次“安检”将潜在的恶意脚本标签、危险属性等剥离或转义确保存入数据库和返回给前端的数据都是“干净”的。Hutool作为一个国产的Java工具库其HtmlUtil类提供了非常强大的HTML过滤和转义功能性能优异且API友好与SpringBoot的过滤器机制结合能让我们用极少的代码构建起坚固的防线。这篇文章我将从一个有十多年后端开发经验的视角带你彻底拆解这个“清洁工”的搭建过程。我不会只给你一段代码让你复制粘贴而是会详细解释每一个配置项背后的安全考量分享我在实际项目中踩过的坑以及如何根据不同的业务场景比如富文本编辑器调整过滤策略。无论你是正在为安全审计发愁的团队骨干还是想夯实自己Web安全基础的初学者这篇全解析都能给你带来可直接落地的解决方案和深度的原理理解。2. 核心思路与架构设计不止于转义在动手写代码之前我们必须想清楚防御策略。一个健壮的XSS防御方案绝不是简单地把script标签删除就万事大吉。攻击者的手段层出不穷从最基础的标签注入到利用HTML事件属性、CSS表达式、甚至混淆编码绕过我们的过滤器需要有相应的应对机制。2.1 防御策略分层我的设计思路是分层防御核心是“输入过滤输出转义”。本过滤器主要解决“输入过滤”这一层。全局输入过滤本过滤器核心在请求进入Controller之前对所有参数进行清洗。这是我们的主战场。上下文输出转义在视图层如Thymeleaf、FreeMarker或API返回时根据输出上下文对动态数据进行转义。这通常由模板引擎或JSON序列化库负责。内容安全策略CSP通过HTTP响应头Content-Security-Policy告诉浏览器只允许加载指定来源的脚本、样式等资源即使有恶意脚本被注入浏览器也不会执行。这是最后一道强有力的防线。我们的SpringBoot Hutool过滤器聚焦于第一层目标是构建一个可配置、高性能的请求清洗网关。2.2 技术选型为什么是Hutool市面上能做HTML过滤的库不少比如Jsoup、OWASP Java Encoder。选择Hutool的HtmlUtil主要基于以下几点实战考量功能聚焦且强大HtmlUtil.filter()方法默认就提供了一套严格的过滤规则能移除所有HTML标签只保留文本内容。这对于大多数普通输入框如用户名、搜索关键词来说开箱即用。同时它也支持白名单过滤模式这对于需要保留部分格式如富文本的场景至关重要。性能与可靠性Hutool在国内Java开发者中应用广泛其稳定性和性能经过大量项目验证。它底层采用高效的字符串处理机制在过滤性能上表现优异不会对应用响应时间造成明显影响。与SpringBoot生态融合好配置简单无需复杂的依赖冲突解决。其API设计符合Java开发者的直觉易于集成到Spring的Filter接口中。国产开源文档友好中文文档和社区支持对于快速解决问题非常有帮助。2.3 过滤器架构设计我们将创建一个自定义的Filter并把它插入到SpringBoot的过滤器链中。关键设计点包括包装HttpServletRequest因为原生的HttpServletRequest中的参数getParameter,getParameterMap是只读的。我们需要自定义一个XssHttpServletRequestWrapper类来包装它重写相关方法在返回值时进行过滤。区分请求类型对于application/x-www-form-urlencoded表单提交和multipart/form-data文件上传格式的参数我们从getParameter*系列方法中过滤。对于application/json格式的请求体我们需要读取输入流进行过滤。配置排除路径像文件上传接口、WebSocket连接点、或一些特殊的第三方回调接口可能不需要或不能进行XSS过滤我们需要支持配置排除列表。线程安全与性能过滤器会被所有线程共享必须确保无状态且高效。避免在过滤方法中创建大量临时对象。这个架构确保了过滤动作对业务代码透明Controller接收到的已经是净化后的数据开发者可以像往常一样编写业务逻辑无需额外关心XSS风险。3. 核心组件实现详解接下来我们进入实战环节一步步构建这个过滤器。我会对每一行关键代码进行解释并说明为什么这么做。3.1 第一步引入依赖首先在你的pom.xml中添加Hutool的依赖。建议使用最新的稳定版本。dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version !-- 请检查并使用最新版本 -- /dependency注意如果你的项目已经引入了SpringBoot的spring-boot-starter-web它已经包含了Servlet API无需额外引入。确保依赖版本兼容一般不会有问题。3.2 第二步创建XSS请求包装器这是过滤器的核心部件负责拦截所有获取参数的请求。import cn.hutool.core.util.StrUtil; import cn.hutool.http.HtmlUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.*; /** * XSS请求包装器 * 重写 getParameter, getParameterValues, getParameterMap, getHeader 等方法 */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } /** * 对单个字符串值进行XSS清理 * param value 原始值 * return 清理后的值 */ private String cleanXss(String value) { if (StrUtil.isBlank(value)) { return value; } // 使用Hutool的HtmlUtil进行过滤移除所有HTML标签保留纯文本 // 这是最严格的过滤方式适用于绝大多数场景 return HtmlUtil.filter(value); } /** * 重写getParameter方法 */ Override public String getParameter(String name) { String value super.getParameter(name); return cleanXss(value); } /** * 重写getParameterValues方法处理多选框等场景 */ Override public String[] getParameterValues(String name) { String[] values super.getParameterValues(name); if (values null) { return null; } int length values.length; String[] cleanValues new String[length]; for (int i 0; i length; i) { cleanValues[i] cleanXss(values[i]); } return cleanValues; } /** * 重写getParameterMap方法 * 注意这里返回的Map中的值数组应该是清理后的副本避免修改原始Map */ Override public MapString, String[] getParameterMap() { MapString, String[] parameterMap super.getParameterMap(); if (parameterMap null) { return null; } MapString, String[] cleanMap new LinkedHashMap(parameterMap.size()); for (Map.EntryString, String[] entry : parameterMap.entrySet()) { String key entry.getKey(); String[] values entry.getValue(); if (values ! null) { String[] cleanValues new String[values.length]; for (int i 0; i values.length; i) { cleanValues[i] cleanXss(values[i]); } cleanMap.put(key, cleanValues); } else { cleanMap.put(key, null); } } return cleanMap; } /** * 重写getHeader方法某些攻击可能通过Header注入 */ Override public String getHeader(String name) { String value super.getHeader(name); return cleanXss(value); } }关键点解析cleanXss方法这里直接使用了HtmlUtil.filter(value)。它会移除字符串中所有的HTML标签。例如输入scriptalert(1)/script你好输出将是你好。重写getParameterMap这是最容易被忽略但很重要的一点。我们必须返回一个新的Map包含清理后的值数组。如果直接修改原Map或返回原Map可能会引起线程安全问题或意外的副作用。处理Header虽然不常见但XSS攻击有时也会尝试通过User-Agent、Referer等HTTP头进行注入。重写getHeader方法是一个防御纵深考虑。3.3 第三步创建并配置XSS过滤器现在我们创建过滤器本体并将其注册到Spring容器。import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; Configuration public class XssFilterConfig { /** * 配置XSS过滤器 */ Bean public FilterRegistrationBeanXssFilter xssFilterRegistrationBean() { FilterRegistrationBeanXssFilter registrationBean new FilterRegistrationBean(); registrationBean.setFilter(new XssFilter()); registrationBean.setOrder(1); // 设置过滤器执行顺序数字越小优先级越高 registrationBean.addUrlPatterns(/*); // 过滤所有请求 registrationBean.setName(xssFilter); // 设置排除路径不需要过滤的URL registrationBean.addInitParameter(exclusions, /api/file/upload,/ws/*,/callback/thirdParty); return registrationBean; } /** * 自定义XSS过滤器 */ public class XssFilter implements Filter { // 排除路径集合 private SetString excludes new HashSet(); Override public void init(FilterConfig filterConfig) throws ServletException { String exclusions filterConfig.getInitParameter(exclusions); if (StrUtil.isNotBlank(exclusions)) { String[] urls exclusions.split(,); excludes.addAll(Arrays.asList(urls)); } } Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; // 判断当前请求路径是否在排除列表中 if (handleExcludeURL(req)) { chain.doFilter(request, response); return; } // 使用我们自定义的包装器包装原始请求 XssHttpServletRequestWrapper xssRequest new XssHttpServletRequestWrapper(req); chain.doFilter(xssRequest, response); } private boolean handleExcludeURL(HttpServletRequest request) { String requestURI request.getRequestURI(); if (StrUtil.isEmpty(requestURI)) { return false; } // 简单路径匹配实际项目中可能需要更复杂的Ant路径匹配 for (String exclude : excludes) { if (exclude.endsWith(/*)) { if (requestURI.startsWith(exclude.substring(0, exclude.length() - 2))) { return true; } } else if (requestURI.equals(exclude)) { return true; } } return false; } Override public void destroy() { // 清理资源 } } }配置详解与避坑指南FilterRegistrationBean这是SpringBoot注册Servlet Filter的标准方式。它提供了比WebFilter注解更强大的控制能力比如设置顺序(setOrder)和初始化参数(addInitParameter)。执行顺序(setOrder)将其设置为1或一个较小的数字是为了让XSS过滤尽可能早地执行。因为过滤要在参数被读取之前完成。但要小心其他全局过滤器如字符编码过滤器CharacterEncodingFilter的顺序通常编码过滤器应该在更前面Order值更小。排除路径(exclusions)这个功能至关重要。例如/api/file/upload文件上传接口过滤可能会破坏文件二进制内容。/ws/*WebSocket端点通常使用不同的协议不需要HTTP参数过滤。/callback/thirdParty第三方回调接口参数格式可能固定且复杂盲目过滤可能导致签名校验失败。避坑提示排除路径的匹配逻辑这里用了简单的字符串匹配。在生产环境中如果路径规则复杂建议引入Spring的AntPathMatcher进行更准确的Ant风格路径匹配如/admin/**。JSON请求体的处理上面的包装器只处理了getParameter和getHeader对于POST请求中application/json类型的数据通过request.getInputStream()读取包装器是无效的。这是一个常见的陷阱。我们需要额外处理。3.4 第四步处理JSON请求体为了处理JSON请求我们需要进一步改造包装器重写getInputStream()方法并缓存流中的数据。// 在 XssHttpServletRequestWrapper 类中添加以下代码 import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.nio.charset.StandardCharsets; public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { // ... 之前已有的字段和方法 ... private byte[] body; // 用于缓存请求体数据 public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 缓存请求体以便多次读取 cacheRequestBody(request); } private void cacheRequestBody(HttpServletRequest request) throws IOException { StringBuilder stringBuilder new StringBuilder(); try (BufferedReader reader request.getReader()) { String line; while ((line reader.readLine()) ! null) { stringBuilder.append(line); } } String requestBody stringBuilder.toString(); if (StrUtil.isNotBlank(requestBody)) { // 关键对JSON字符串进行XSS过滤 // 注意这里简单过滤整个字符串可能会破坏JSON结构。更优方案是解析JSON后对字符串值字段单独过滤。 String cleanBody cleanXssJson(requestBody); this.body cleanBody.getBytes(StandardCharsets.UTF_8); } else { this.body new byte[0]; } } /** * 专门处理JSON字符串的XSS清理 * 简单实现直接调用HtmlUtil.filter但这会过滤掉JSON中的引号等符号破坏结构。 * 正确做法使用JSON库解析然后递归遍历所有字符串值进行过滤。 * 此处为演示先使用简单方法。下文会给出优化方案。 */ private String cleanXssJson(String json) { // 【警告】此简单方法会破坏JSON结构仅作演示。 // return HtmlUtil.filter(json); // 暂时先原样返回我们在后面优化。 return json; } Override public ServletInputStream getInputStream() throws IOException { if (body null) { body new byte[0]; } final ByteArrayInputStream byteArrayInputStream new ByteArrayInputStream(body); return new ServletInputStream() { Override public int read() throws IOException { return byteArrayInputStream.read(); } Override public boolean isFinished() { return byteArrayInputStream.available() 0; } Override public boolean isReady() { return true; } Override public void setReadListener(ReadListener listener) { // 不处理异步 } }; } Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8)); } }这里暴露了一个严重问题HtmlUtil.filter会无差别地过滤掉、、、等字符这对于JSON字符串来说是灾难性的因为JSON本身就需要这些字符作为语法符号如双引号。直接过滤会导致JSON解析失败。解决方案我们需要解析JSON只对值部分value的字符串类型进行过滤。这需要引入一个JSON处理库如Jackson。3.5 第五步优化JSON请求体的精准过滤首先确保你的项目引入了JacksonSpringBoot默认已包含。!-- SpringBoot web starter 默认包含 jackson -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency然后我们优化cleanXssJson方法使用Jackson遍历JSON节点。import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.*; // 在 XssHttpServletRequestWrapper 类中修改 cleanXssJson 方法并添加辅助方法 private String cleanXssJson(String json) throws IOException { if (StrUtil.isBlank(json)) { return json; } ObjectMapper objectMapper new ObjectMapper(); try { JsonNode rootNode objectMapper.readTree(json); JsonNode cleanedNode cleanJsonNode(rootNode); return objectMapper.writeValueAsString(cleanedNode); } catch (IOException e) { // 如果JSON解析失败可能是无效JSON或非JSON内容直接返回原字符串或进行安全处理 // 出于安全考虑对于无法解析的JSON可以选择记录日志并返回空对象或原串有风险 // 这里记录日志后返回原串实际项目可根据安全级别调整 // log.warn(XSS过滤JSON解析失败内容将被跳过过滤。内容前缀{}, StrUtil.subPre(json, 100)); return json; // 或返回 {} } } private JsonNode cleanJsonNode(JsonNode node) { if (node null || node.isNull()) { return NullNode.getInstance(); } if (node.isTextual()) { // 只对文本节点进行XSS过滤 String cleanedText HtmlUtil.filter(node.asText()); return new TextNode(cleanedText); } if (node.isArray()) { ArrayNode newArray new ArrayNode(JsonNodeFactory.instance); for (JsonNode childNode : node) { newArray.add(cleanJsonNode(childNode)); } return newArray; } if (node.isObject()) { ObjectNode newObject new ObjectNode(JsonNodeFactory.instance); node.fields().forEachRemaining(entry - { newObject.set(entry.getKey(), cleanJsonNode(entry.getValue())); }); return newObject; } // 对于数字、布尔值等非字符串节点直接返回 return node; }优化点解析精准过滤cleanJsonNode方法递归遍历JSON树只对TextNode字符串值调用HtmlUtil.filter。对象键key通常不可控但理论上也应考虑过滤不过修改key会破坏数据结构需谨慎。这里假设key是安全的。异常处理如果输入的字符串不是合法JSONobjectMapper.readTree会抛出异常。在catch块中我们记录了警告注释掉了实际项目应开启日志并选择返回原字符串。这是一个安全权衡严格模式下可以拒绝非法请求宽松模式下为了兼容性可能放过。我建议在关键业务系统采用严格模式直接返回错误响应。性能考虑每次请求都进行JSON解析和序列化对性能有一定影响。如果应用JSON请求体非常大或QPS极高需要评估性能损耗。可以考虑使用更高效的JSON库如fastjson2但需注意其历史安全漏洞或者对于明确内部、可信的接口通过排除路径跳过过滤。至此一个能够处理表单参数、URL参数、HTTP头和JSON请求体的完整XSS过滤器就构建完成了。4. 高级配置与场景化调优基础的过滤器搭建好了但真实业务场景往往更复杂。直接移除所有HTML标签的“一刀切”策略可能会误伤一些合法需求。4.1 场景一富文本编辑器内容处理用户在使用富文本编辑器如UEditor、WangEditor、TinyMCE提交文章、评论时内容本身包含HTML标签如p,strong,img。我们显然不能把这些标签都删掉。解决方案使用白名单过滤。Hutool的HtmlUtil提供了cleanHtml方法可以指定一个白名单。import cn.hutool.core.util.StrUtil; import cn.hutool.http.HtmlUtil; // 在 XssHttpServletRequestWrapper 中新增一个方法或修改 cleanXss 方法使其可配置 private String cleanXss(String value, boolean isRichText) { if (StrUtil.isBlank(value)) { return value; } if (isRichText) { // 使用白名单过滤 // 定义允许的标签及其属性 // 这是一个相对宽松的白名单可根据实际需求收紧 String clean HtmlUtil.cleanHtml(value, new HashSet(Arrays.asList( // 允许的标签 p, br, b, strong, i, em, u, s, span, div, h1, h2, h3, h4, h5, h6, ul, ol, li, a, // 链接 img, // 图片 blockquote, code, pre, table, thead, tbody, tr, th, td ))); // 进一步可以限制标签的属性例如只允许a标签有href和title属性 // 这里Hutool的cleanHtml默认会移除所有不安全的属性如onclick, javascript:等 // 对于更精细的控制可能需要结合Jsoup return clean; } else { // 普通文本严格过滤移除所有标签 return HtmlUtil.filter(value); } }那么如何判断一个字段是否是富文本内容呢有两种常见思路接口约定为接收富文本的接口设计独立的DTO或使用特定的参数名如contentHtml在过滤器或Controller中根据字段名判断。注解标记自定义一个注解如RichText在DTO的字段上标注。然后通过AOP或自定义参数解析器在数据绑定后对标记了注解的字段进行白名单过滤。这种方式更优雅但实现稍复杂。实操心得白名单策略务必收紧。例如a标签的href属性必须严格校验协议只允许http://,https://,mailto:绝对禁止javascript:。img标签的src属性也应限制为可信的域名或Base64 Data URL需注意大小。建议参考OWASP的XSS防护白名单推荐。4.2 场景二忽略特定字段或类型的过滤有些字段可能包含需要原样存储的代码片段、模板语法或加密数据过滤会破坏其功能。解决方案在包装器中增加忽略逻辑。我们可以通过配置一个“忽略字段名”列表来实现。public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final SetString IGNORE_FIELDS new HashSet(Arrays.asList(signature, encryptedData, codeSnippet)); // ... 其他代码 ... private String cleanXss(String value, String parameterName) { if (StrUtil.isBlank(value) || IGNORE_FIELDS.contains(parameterName)) { return value; // 忽略字段直接返回 } // ... 后续判断是否是富文本字段然后调用相应的过滤方法 ... return HtmlUtil.filter(value); } // 然后修改 getParameter, getParameterValues 等方法将参数名传入 cleanXss Override public String getParameter(String name) { String value super.getParameter(name); return cleanXss(value, name); } }注意事项忽略字段必须谨慎评估其安全性。确保这些字段的值来自绝对可信的来源如系统自身生成的签名、来自可信合作伙伴的加密数据或者后续有独立的安全校验流程。4.3 场景三性能优化与缓存在高并发场景下频繁的字符串过滤和JSON解析可能成为瓶颈。优化思路路径排除精细化将确无需过滤的API如健康检查/actuator/health、内部状态接口加入排除列表减少不必要的过滤开销。缓存过滤结果对于相同的恶意模式如常见的script过滤结果总是相同的。可以考虑使用一个简单的LRU缓存如使用Collections.synchronizedMap包装LinkedHashMap或使用Caffeine库来缓存(原始字符串, 过滤后字符串)键值对。但要注意缓存大小和字符串长度避免内存溢出。异步过滤对于非关键路径或可接受轻微延迟的请求可以考虑将过滤操作放入单独的线程池处理。但这会显著增加复杂度需要权衡。使用更快的库如果性能测试发现HtmlUtil.filter是热点可以评估其他库如OWASP Java Encoder的forHtmlContent方法它在某些基准测试中可能更快。我的经验在99%的业务系统中上述基础过滤器的性能开销是可以接受的。过早优化是万恶之源。建议先上线基础版本通过APM工具如SkyWalking, Pinpoint监控过滤器的平均耗时和P99耗时确认其成为瓶颈后再进行优化。我曾在一个人均QPS过千的系统中使用类似方案CPU增幅不到2%完全在可接受范围。5. 测试、部署与问题排查5.1 如何测试过滤器的有效性不要只靠“感觉”必须进行系统化测试。单元测试为XssHttpServletRequestWrapper和cleanXss方法编写单元测试。Test void testCleanXss() { XssHttpServletRequestWrapper wrapper ... // 需要Mock HttpServletRequest String input scriptalert(xss)/scriptp正常文本/p; String output wrapper.cleanXss(input, false); Assertions.assertThat(output).isEqualTo(正常文本); // 测试富文本白名单 String richInput p一段strong加粗/strong文本/pscriptalert(1)/script; String richOutput wrapper.cleanXss(richInput, true); Assertions.assertThat(richOutput).contains(p, strong); Assertions.assertThat(richOutput).doesNotContain(script); }集成测试使用MockMvc或TestRestTemplate对真实接口发起包含XSS payload的请求断言响应中不包含恶意脚本且业务逻辑正确。SpringBootTest AutoConfigureMockMvc class UserControllerTest { Test void testCreateUserWithXss(Autowired MockMvc mvc) throws Exception { String maliciousName 张三scriptalert(1)/script; mvc.perform(post(/api/user) .contentType(MediaType.APPLICATION_JSON) .content({\username\: \ maliciousName \})) .andExpect(status().isOk()) .andExpect(jsonPath($.data.username).value(张三)); // 期望过滤后的名字 } }手动渗透测试使用浏览器或工具如Burp Suite手动尝试各种XSS payload包括大小写混淆、编码绕过、利用HTML事件等。以下是一些测试向量scriptalert(1)/scriptimg srcx onerroralert(1)svg/onloadalert(1)javascript:alert(1)(在a href中测试)scriptalert(1)/script(测试是否破坏了HTML属性上下文)5.2 常见问题排查实录在实际部署和运行中你可能会遇到以下问题问题1过滤器导致application/json请求的RequestBody对象绑定失败。现象Controller方法参数使用RequestBody接收的对象所有字段都为null。原因我们的XssHttpServletRequestWrapper重写了getInputStream()但Spring的HttpMessageConverter如MappingJackson2HttpMessageConverter在读取流时如果流已经被读取过我们在cacheRequestBody中读了并且没有正确重置就会导致读取不到数据。解决方案我们的实现中cacheRequestBody在构造器中读取了流并存入byte[] body然后重写的getInputStream()返回的是基于这个body的新流。这是正确的做法确保了流可重复读。如果出现问题检查是否在其他地方如另一个过滤器提前消费了请求体且未包装。确保XSS过滤器在过滤器链中顺序靠前。问题2文件上传multipart/form-data接口出错。现象上传文件时接口报错或文件损坏。原因multipart请求的解析通常由MultipartResolver如StandardServletMultipartResolver处理它会在过滤器链之前或特定阶段解析请求将文件部分和表单字段部分分开。我们的过滤器对原始参数进行过滤可能会干扰这个过程。解决方案将文件上传接口的路径加入到过滤器的排除列表exclusions中。这是最安全、最直接的做法。文件内容本身是二进制流不应进行字符串过滤。问题3过滤后前端显示出现了奇怪的amp;或lt;。现象用户输入了或存入数据库的是amp;或lt;前端显示也是这些字符实体。原因HtmlUtil.filter默认会进行HTML转义。输入a b会被转义成a lt; b。这是正确的安全行为防止了HTML注入。解决方案这通常不是过滤器的问题而是输出上下文的问题。在前端渲染时如果你使用的是类似{{ content }}的语法Thymeleaf, FreeMarker默认会转义那么lt;会被正确渲染为显示在页面上。如果你是在非HTML上下文如纯文本、JSON API中返回或者前端使用了v-htmlVue等危险操作那么你需要决定是否在输出时进行反转义。我的建议是保持数据库存储转义后的安全文本。在前端除非确需渲染HTML如富文本内容否则永远使用文本插值而非HTML插值。对于需要渲染HTML的情况确保内容来自可信来源如经过白名单过滤的富文本。问题4某些复杂的、编码过的XSS Payload似乎绕过了过滤。现象输入img srcx onerror#x61;#x6c;#x65;#x72;#x74;#x28;#x31;#x29;可能未被完全过滤。原因Hutool的HtmlUtil.filter默认能处理常见的HTML实体和十进制/十六进制编码。但攻击者的编码方式千奇百怪如JSFuck、多重编码。解决方案标准化输入在过滤前尝试对输入进行规范化或解码。但要注意这可能引入新的复杂度。依赖更强大的库例如OWASP Java Encoder它提供了针对不同上下文HTML内容、HTML属性、JavaScript、CSS、URL的编码器遵循“输出编码”原则可能更安全。纵深防御记住过滤器只是第一层。务必启用CSPContent-Security-Policy。即使有脚本被注入一个严格的CSP也能阻止浏览器执行它。在SpringBoot中可以很容易地通过配置或HttpSecurity来添加CSP头。定期更新与审计关注安全社区更新Hutool等依赖库到最新版本定期进行安全扫描和渗透测试。5.3 部署上线检查清单在将这套过滤器部署到生产环境前请对照此清单进行检查[ ]依赖检查Hutool版本是否稳定是否存在已知安全漏洞[ ]排除路径是否已将所有文件上传、WebSocket、第三方回调等特殊接口路径加入排除列表[ ]富文本处理如果业务涉及富文本白名单是否已根据最小权限原则配置完成是否测试过各种合法标签和属性[ ]JSON处理JSON过滤逻辑是否经过测试确保不会破坏正常JSON结构[ ]性能基线在测试环境对关键接口进行压测记录增加过滤器前后的RT响应时间和CPU使用率变化确保在可接受范围内。[ ]监控与日志是否在过滤器中添加了必要的WARN或ERROR日志例如JSON解析失败监控系统是否能捕获这些日志[ ]回滚方案是否准备了快速禁用过滤器的方案如通过配置开关以防出现不可预见的兼容性问题。[ ]安全测试是否进行了至少一轮涵盖反射型、存储型XSS的渗透测试最后我想强调的是没有任何单一技术能提供100%的安全。这个基于SpringBoot和Hutool的XSS过滤器是一个强大、实用的基础防御组件它能帮你挡住绝大部分自动化攻击和常见的手工测试。但真正的安全是一个持续的过程需要将安全编码规范如对输出进行编码、定期依赖更新、安全特性配置如CSP、HttpOnly Cookie和安全意识培训结合起来。把这个过滤器当作你应用安全铠甲中的一块坚实护板然后继续构建和完善你的整体防御体系吧。