Java 转安全开发:写 1 个 XSS 过滤组件,复用 Spring 基础
Java 转安全开发:写 1 个 XSS 过滤组件,复用 Spring 基础

“做了 2 年 Java 开发,熟悉 Spring Boot、Spring MVC,想转安全开发却不知道从哪入手;听说 XSS 是 Web 高频漏洞,却只会用replace替换特殊字符,不知道怎么系统性防护”—— 这是多数 Java 开发者转型安全开发的初始困境。
其实利用你熟悉的 Spring 技术栈,就能开发企业级 XSS 过滤组件,核心用到 Spring MVC 拦截器、HttpServletRequestWrapper参数包装,这些都是你日常开发中接触过的技术。本文组件覆盖 “普通表单参数” 和 “HTML 富文本” 两种场景,代码可直接集成到 Spring Boot 项目,3 小时完成开发,还能作为安全开发实战成果写进简历,比 “会用工具” 更有竞争力。
一、组件定位:Java 开发者能快速上手的 “企业级 XSS 防护”
1. 核心价值(贴合转行需求)
-
复用 Spring 基础:基于 Spring MVC 拦截器(HandlerInterceptor)、请求包装(HttpServletRequestWrapper)开发,不用学新框架,Java 开发者能快速理解;
-
覆盖双场景防护:
-
普通参数过滤:如登录框、搜索框输入的,直接转义特殊字符(<→<);
-
富文本过滤:如博客编辑器、商品描述的 HTML 内容,保留
等安全标签,删除
2. 技术栈(全是 Java 开发者熟悉的工具)
| 技术 / 依赖 | 作用 | 复用点 |
|---|---|---|
| Spring Boot 2.7.x | 基础 Web 框架 | 日常开发使用的 Spring 生态,无学习成本 |
| Spring MVC Interceptor | 拦截所有 HTTP 请求,触发 XSS 过滤 | 类似登录拦截器的配置逻辑,可直接复用 |
| HttpServletRequestWrapper | 包装请求参数,修改参数值 | Java Web 基础 API,处理请求参数的常用方式 |
| Jsoup 1.17.2 | HTML 富文本过滤(开源库) | 类似 FastJson 的第三方库集成,配置简单 |
二、组件设计思路:3 步实现 XSS 过滤(Java 开发者易理解)
利用 Spring MVC 的请求处理流程,在 “请求到达 Controller 前” 插入 XSS 过滤逻辑,全程不侵入业务代码,符合 “开闭原则”:
-
拦截请求:用HandlerInterceptor拦截所有POST/GET请求,判断是否需要过滤;
-
包装参数:用HttpServletRequestWrapper重写getParameter(普通参数)、getParameterMap(多值参数)方法,在获取参数时自动过滤 XSS;
-
分场景过滤:
-
普通参数:用字符替换转义</>/'/"等特殊字符;
-
富文本参数:用 Jsoup 按白名单过滤 HTML,保留安全标签(如
),删除危险标签和事件。
三、分步编码:3 小时完成开发(带详细注释)
以 Spring Boot 2.7.x 为例,按 “过滤工具类→请求包装类→拦截器→配置类” 顺序开发,每个类功能独立,可复制粘贴后直接使用。
1. 第一步:引入依赖(pom.xml)
仅需添加 Jsoup 依赖(处理富文本过滤),Spring 相关依赖已包含在spring-boot-starter-web中:
<dependencies>
<!-- Spring Web基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Jsoup:HTML解析与过滤(富文本XSS防护核心) -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
<!-- Lombok:简化代码(可选,不用也可手动写getter/setter) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 第二步:开发 XSS 过滤工具类(核心逻辑)
封装 “普通参数过滤” 和 “富文本过滤” 的核心方法,后续拦截器和包装类直接调用:
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
/**
* XSS过滤工具类:提供普通参数和富文本的过滤方法
* 复用点:类似项目中的CommonUtils工具类,开发思路一致
*/
public class XssFilterUtils {
/**
* 1. 普通参数XSS过滤(如表单输入、URL参数)
* 逻辑:转义特殊字符,避免脚本执行
*/
public static String filterSimpleParam(String value) {
if (value == null || value.trim().isEmpty()) {
return value;
}
// 转义核心特殊字符:< > ' " \ / &
return value.replace("<", "<")
.replace(">", ">")
.replace("'", "'")
.replace("\"", """)
.replace("\\", "\")
.replace("/", "/")
.replace("&", "&");
}
/**
* 2. 富文本XSS过滤(如博客正文、商品描述)
* 逻辑:用Jsoup按白名单保留安全标签,删除危险内容
*/
public static String filterRichText(String html) {
if (html == null || html.trim().isEmpty()) {
return html;
}
// 定义安全标签白名单:允许<p><img><h1-h3><a>等常用标签
Safelist safeList = Safelist.basic() // 基础白名单(<b><i><u>等)
.addTags("p", "img", "h1", "h2", "h3", "a") // 新增允许的标签
.addAttributes("img", "src", "alt") // 允许img标签的src/alt属性
.addAttributes("a", "href", "target") // 允许a标签的href/target属性
.addProtocols("a", "href", "http", "https") // a标签href仅允许http/https协议
.addProtocols("img", "src", "http", "https", "data"); // img允许data协议(base64图片)
// 执行过滤:清理HTML中的危险标签和事件(如<script>、onerror)
return Jsoup.clean(
html,
safeList // 安全白名单
);
}
/**
* 批量过滤参数数组(如多选框的多个值)
*/
public static String[] filterParamArray(String[] values) {
if (values == null || values.length == 0) {
return values;
}
String[] filteredValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
filteredValues[i] = filterSimpleParam(values[i]);
}
return filteredValues;
}
}
3. 第三步:开发请求包装类(修改参数值)
继承HttpServletRequestWrapper,重写参数获取方法,在获取参数时自动触发 XSS 过滤 —— 这是 Java Web 处理 “修改请求参数” 的标准方式,复用你熟悉的ServletRequest API:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Map;
/**
* XSS请求包装类:重写参数获取方法,实现参数过滤
* 复用点:类似处理请求编码、参数解密的包装类,开发模式一致
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
// 标记是否为富文本参数(需特殊处理)
private final Map<String, Boolean> richTextParams;
/**
* 构造方法:传入原始请求和富文本参数名列表
* @param request 原始HttpServletRequest
* @param richTextParamNames 富文本参数名(如"content"、"description")
*/
public XssHttpServletRequestWrapper(HttpServletRequest request, String[] richTextParamNames) {
super(request);
// 初始化富文本参数映射(方便快速判断)
this.richTextParams = new java.util.HashMap<>();
if (richTextParamNames != null) {
for (String paramName : richTextParamNames) {
this.richTextParams.put(paramName.trim().toLowerCase(), Boolean.TRUE);
}
}
}
/**
* 1. 重写getParameter:处理单个参数(如request.getParameter("username"))
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (name == null) {
return XssFilterUtils.filterSimpleParam(value);
}
// 判断是否为富文本参数,选择对应过滤方法
if (richTextParams.containsKey(name.trim().toLowerCase())) {
return XssFilterUtils.filterRichText(value);
} else {
return XssFilterUtils.filterSimpleParam(value);
}
}
/**
* 2. 重写getParameterValues:处理数组参数(如request.getParameterValues("hobby"))
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (name == null || values == null) {
return XssFilterUtils.filterParamArray(values);
}
// 富文本参数暂不支持数组(通常富文本是单个值),直接用普通过滤
return XssFilterUtils.filterParamArray(values);
}
/**
* 3. 重写getParameterMap:处理所有参数映射(如@RequestParam Map<String, Object> params)
*/
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> paramMap = super.getParameterMap();
if (paramMap.isEmpty()) {
return paramMap;
}
// 遍历所有参数,逐个过滤
Map<String, String[]> filteredMap = new java.util.HashMap<>();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
String paramName = entry.getKey();
String[] paramValues = entry.getValue();
// 富文本参数过滤
if (richTextParams.containsKey(paramName.trim().toLowerCase())) {
String[] richValues = new String[paramValues.length];
for (int i = 0; i < paramValues.length; i++) {
richValues[i] = XssFilterUtils.filterRichText(paramValues[i]);
}
filteredMap.put(paramName, richValues);
} else {
// 普通参数过滤
filteredMap.put(paramName, XssFilterUtils.filterParamArray(paramValues));
}
}
return filteredMap;
}
}
4. 第四步:开发 XSS 拦截器(触发过滤逻辑)
用 Spring MVC 的HandlerInterceptor拦截所有请求,将原始请求包装为XssHttpServletRequestWrapper—— 类似你平时写的 “登录拦截器”“日志拦截器”,配置逻辑完全复用:
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* XSS拦截器:拦截请求并包装为XSS过滤请求
* 复用点:Spring MVC拦截器开发,和登录验证、接口限流拦截器逻辑一致
*/
public class XssInterceptor implements HandlerInterceptor {
// 富文本参数名列表(可配置,如"content"、"articleBody")
private final String[] richTextParams;
// 构造方法:注入富文本参数名
public XssInterceptor(String[] richTextParams) {
this.richTextParams = richTextParams;
}
/**
* 请求处理前拦截:包装请求
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 排除不需要过滤的请求(如静态资源、Swagger文档)
String requestUri = request.getRequestURI();
if (isExcludeUri(requestUri)) {
return true; // 跳过过滤
}
// 将原始请求包装为XSS过滤请求,后续Controller获取的参数已过滤
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(request, richTextParams);
// 替换请求对象(核心步骤:让后续流程使用包装后的请求)
request = xssRequest;
// 继续执行后续流程(如Controller处理)
return true;
}
/**
* 排除不需要过滤的URI(静态资源、API文档等)
*/
private boolean isExcludeUri(String uri) {
// 排除规则:可配置在application.yml中,这里简化为硬编码
String[] excludeUris = {
"/static/", "/css/", "/js/", "/img/", // 静态资源
"/swagger-ui/", "/v3/api-docs/", // Swagger文档
"/favicon.ico" // 图标文件
};
for (String exclude : excludeUris) {
if (uri.startsWith(exclude)) {
return true;
}
}
return false;
}
}
5. 第五步:配置类(启用 XSS 拦截器)
用 Spring 的WebMvcConfigurer配置拦截器,实现 “一键启用”—— 复用你平时配置拦截器、资源映射的代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Spring MVC配置类:注册XSS拦截器
* 复用点:WebMvcConfigurer配置,和配置CORS、拦截器顺序逻辑一致
*/
@Configuration
public class XssFilterConfig implements WebMvcConfigurer {
/**
* 1. 定义富文本参数名(可从application.yml读取,这里简化为硬编码)
* 说明:这些参数会用富文本过滤逻辑(保留安全HTML标签)
*/
private static final String[] RICH_TEXT_PARAMS = {"content", "articleBody", "description"};
/**
* 2. 注册XSS拦截器Bean(注入富文本参数)
*/
@Bean
public XssInterceptor xssInterceptor() {
return new XssInterceptor(RICH_TEXT_PARAMS);
}
/**
* 3. 添加拦截器到Spring MVC拦截链
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(xssInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/static/**", "/swagger-ui/**", "/v3/api-docs/**"); // 排除不需要过滤的路径
}
}
四、实战测试:验证组件效果(Java 开发者熟悉的测试方式)
开发完成后,用 Spring Boot 写一个简单的 Controller 测试,验证普通参数和富文本参数的过滤效果:
1. 编写测试 Controller
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* XSS过滤测试Controller
*/
@RestController
@RequestMapping("/test/xss")
public class XssTestController {
/**
* 测试1:普通参数过滤(如用户名、密码)
*/
@PostMapping("/simple")
public String testSimpleParam(
@RequestParam("username") String username,
@RequestParam("password") String password) {
// 返回过滤后的参数(验证是否转义)
return String.format("""
普通参数过滤结果:
username:%s
password:%s""", username, password);
}
/**
* 测试2:富文本参数过滤(如博客内容)
*/
@PostMapping("/rich")
public String testRichText(
@RequestParam("title") String title, // 普通参数
@RequestParam("content") String content) { // 富文本参数(配置在RICH_TEXT_PARAMS中)
return String.format("""
富文本参数过滤结果:
title(普通参数):%s
content(富文本参数):%s""", title, content);
}
}
2. 用 Postman 测试(步骤 + 预期结果)
测试 1:普通参数过滤
-
请求 URL:http://localhost:8080/test/xss/simple
-
请求参数(Form Data):
username=admin
password=123456’ or ‘1’='1
- 预期结果(特殊字符已转义):
普通参数过滤结果:
username:<script>alert('xss')</script>admin
password:123456' or '1'='1
测试 2:富文本参数过滤
-
请求 URL:http://localhost:8080/test/xss/rich
-
请求参数(Form Data):
title=标题(普通参数)
content=
安全内容
- 预期结果(危险标签 / 事件被删除,安全标签保留):
富文本参数过滤结果:
title(普通参数):<img src=x onerror=alert(1)>标题
content(富文本参数):<p>安全内容</p><img src="x">
五、避坑指南:Java 开发者最易踩的 3 个问题
1. 坑 1:@RequestBody 参数过滤失效(如 JSON 参数)
-
原因:当前组件仅处理Form Data/URL参数,@RequestBody接收的 JSON 参数会绕过HttpServletRequestWrapper;
-
解决:添加RequestBodyAdvice处理 JSON 参数,复用XssFilterUtils:
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import java.io.IOException;
@ControllerAdvice
public class XssRequestBodyAdvice extends RequestBodyAdviceAdapter {
// 重写beforeBodyRead,过滤JSON参数
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Class<? extends HttpMessageConverter<?>> converterType, Object targetType) {
// 这里需要递归遍历JSON对象的所有字段,调用XssFilterUtils过滤
// 简化示例:若body是String类型(如纯JSON字符串)
if (body instanceof String) {
return XssFilterUtils.filterSimpleParam((String) body);
}
// 复杂JSON对象需用反射遍历字段,可引入FastJson/Jackson工具类
return body;
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true; // 对所有@RequestBody生效
}
}
2. 坑 2:富文本过滤后丢失正常标签(如
)
-
原因:Jsoup 的Safelist.basic()默认不包含
标签; -
解决:在filterRichText方法中添加标签到白名单:
Safelist safeList = Safelist.basic()
.addTags("div", "span") // 新增允许的<div><span>标签
.addAttributes("div", "class", "style"); // 允许div的class/style属性
3. 坑 3:拦截器未生效(请求未被过滤)
-
原因 1:拦截器路径配置错误(如addPathPatterns(“/api/**”)只拦截 /api 路径,遗漏其他路径);
-
解决:用addPathPatterns(“/**”)拦截所有路径,再通过excludePathPatterns排除不需要的路径;
-
原因 2:Spring Boot 版本问题(如 Spring Boot 3.x 需用WebMvcConfigurer而非WebMvcConfigurerAdapter);
-
解决:确保XssFilterConfig实现的是org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口。
六、成果转化:转行安全开发的 “简历亮点”
1. 简历怎么写(突出 Spring 复用和安全价值)
✅ 正确写法:
“基于 Spring MVC 拦截器与 HttpServletRequestWrapper 开发 XSS 过滤组件,覆盖‘普通表单参数转义’和‘富文本 HTML 白名单过滤’双场景,集成 Jsoup 实现危险标签 / 事件清理,支持 Spring Boot 2.x/3.x 一键配置。组件已集成到企业博客项目,拦截 95% 以上 XSS 攻击,减少安全漏洞修复时间 60%,具备安全组件设计与落地能力。”
❌ 错误写法:
“会用 Java 写 XSS 过滤组件,懂 Jsoup 库的使用。”
2. 面试演示技巧(展示技术深度)
-
演示准备:本地启动 Spring Boot 项目,用 Postman 发送含 XSS 脚本的请求,展示过滤前后的参数对比;
-
技术讲解:重点讲 “如何复用 Spring 技术”:
“这个组件的核心是利用 Spring MVC 的拦截器机制,在请求到达 Controller 前触发过滤,通过 HttpServletRequestWrapper 包装参数 —— 这和我们平时做登录拦截、参数解密的逻辑一致,只是把业务逻辑换成了 XSS 过滤,开发效率很高。”
- 扩展思考:主动提 “优化方向”(如从配置文件读取白名单、支持 JSON 参数过滤),体现安全开发思维。
最后:Java 转安全开发的 “核心优势”
很多 Java 开发者觉得转安全开发需要学很多新东西,但从这个组件能看出:
-
你熟悉的 Spring MVC 拦截器,是安全组件的 “触发入口”;
-
你常用的HttpServletRequestWrapper,是参数过滤的 “核心载体”;
-
你习惯的第三方库集成(如 FastJson、Jsoup),是安全功能的 “实现工具”。
你的 Java 开发经验不是 “负担”,而是转安全开发的 “天然优势”—— 重点是把 “业务开发思维” 转化为 “安全防护思维”,比如把 “处理请求参数” 变成 “过滤危险参数”,把 “开发业务组件” 变成 “开发安全组件”。
这个 XSS 过滤组件只是起点,后续还能基于 Spring 开发 “SQL 注入防护组件”“接口签名验证组件”,逐步构建你的安全开发技能体系。
网络安全学习资料分享
为了帮助大家更好的学习网络安全,我把我从一线互联网大厂薅来的网络安全教程及资料分享给大家,里面的内容都是适合零基础小白的笔记和资料,不懂编程也能听懂、看懂,朋友们如果有需要这套网络安全教程+进阶学习资源包,可以扫码下方二维码限时免费领取(如遇扫码问题,可以在评论区留言领取哦)~


更多推荐


所有评论(0)