如何在过滤器中读取Multipart请求且不消耗请求本身以实现XSS防护?
解决遗留应用Multipart-FormData请求的XSS防护问题
太懂这种遗留系统踩坑的痛苦了!之前的请求包装器只能处理urlencoded表单,碰到multipart就失效,试过的方案要么不兼容现有功能,要么直接把请求搞废。结合你的场景,这里有几个能在不消耗请求的前提下实现XSS防护的思路:
方案1:基于Servlet 3.0+ Part API做缓存式请求包装
Servlet 3.0及以上版本自带的Part API是处理multipart请求的标准方式,我们可以做一个带缓存的HttpServletRequestWrapper,提前把所有请求内容读出来清理并缓存,后续应用拿参数时直接从缓存取,不会碰原始请求流。
具体步骤:
- 写一个
MultipartXssSafeRequestWrapper继承HttpServletRequestWrapper - 在构造方法里先判断是否是multipart请求:
request.getContentType() != null && request.getContentType().startsWith("multipart/form-data") - 如果是,调用
request.getParts()遍历所有Part:- 对于文本类型的Part(通过
part.getContentType()判断,或者识别表单字段),读取它的内容,用你现有的XSS清理逻辑处理后,存入一个Map<String, List<String>>缓存起来 - 对于文件类型的Part,直接把Part对象缓存到集合里,不修改文件内容
- 对于文本类型的Part(通过
- 重写
getParameter、getParameterValues、getParameterMap这些方法,优先返回缓存里的清理后值 - 重写
getPart、getParts方法,返回缓存的Part(文本Part确保返回清理后的内容,文件Part直接返回原对象)
这个方案的好处是完全兼容Servlet标准,不需要额外依赖,也不会破坏现有的Angular文件上传功能——因为文件Part是原样缓存的,后续应用读取文件流不受影响。
方案2:扩展Commons FileUpload的工厂类做源头清理
如果你之前项目里用了Commons FileUpload,可以自定义一个FileItemFactory,在生成FileItem的时候就把文本参数清理好,同时缓存所有解析后的内容,避免重复读取请求流。
怎么做:
- 继承
DiskFileItemFactory,重写createItem方法 - 当创建的是文本字段的FileItem时,先读取它的内容,用你的XSS逻辑清理后再存回去;如果是文件字段,直接返回原FileItem
- 在过滤器里,用这个自定义工厂类来解析multipart请求,把解析后的文本参数存入缓存,然后让请求包装器的参数获取方法返回这些清理后的值
- 关键是要把解析后的FileItem集合缓存起来,后续应用需要获取文件时,直接从缓存里拿,不会再去读原始请求流
这个方案适合已经依赖Commons FileUpload的项目,复用现有解析逻辑,改动量比较小。
方案3:容器级配置+请求包装器的兼容方案
如果你的容器是Tomcat,不想改太多代码,可以试试调整容器的multipart配置,但避开allowCasualMultipartParsing=true这个坑:
- 在
web.xml里给需要处理multipart的Servlet添加<multipart-config>配置(指定临时文件路径、大小限制等) - 然后在请求包装器里,对于multipart请求,通过Servlet的
getParts()方法读取参数并清理,缓存后返回
这个方案要注意不同容器的multipart配置差异,比如Jetty和Tomcat的配置略有不同,但核心都是用容器的标准API来读取,避免自己解析导致请求流被消耗。
必须避的坑
- 绝对不要直接读请求流然后不重置!一定要把读取后的内容缓存成字节数组,并重写
getInputStream和getReader方法返回缓存的流,否则后续应用会读不到请求内容 - 文件Part只需要清理文件名(如果有XSS风险的话),不要修改文件的二进制内容,不然上传的文件会损坏
- 复用你之前已经验证过的XSS清理逻辑,不要随便换规则,确保urlencoded和multipart请求的防护标准一致
应该能解决你的问题了,祝你早日搞定这个棘手的bug!
内容的提问来源于stack exchange,提问作者CSoft




