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 过滤逻辑,全程不侵入业务代码,符合 “开闭原则”:

  1. 拦截请求:用HandlerInterceptor拦截所有POST/GET请求,判断是否需要过滤;

  2. 包装参数:用HttpServletRequestWrapper重写getParameter(普通参数)、getParameterMap(多值参数)方法,在获取参数时自动过滤 XSS;

  3. 分场景过滤

  • 普通参数:用字符替换转义</>/'/"等特殊字符;

  • 富文本参数:用 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("<", "&lt;")
                   .replace(">", "&gt;")
                   .replace("'", "&#39;")
                   .replace("\"", "&quot;")
                   .replace("\\", "&#92;")
                   .replace("/", "&#47;")
                   .replace("&", "&amp;");
    }

    /**
     * 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:&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;admin
password:123456&#39; or &#39;1&#39;=&#39;1
测试 2:富文本参数过滤
  • 请求 URL:http://localhost:8080/test/xss/rich

  • 请求参数(Form Data):

title=标题(普通参数)

content=

安全内容

(富文本参数)
  • 预期结果(危险标签 / 事件被删除,安全标签保留):
富文本参数过滤结果:
title(普通参数):&lt;img src=x onerror=alert(1)&gt;标题
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 注入防护组件”“接口签名验证组件”,逐步构建你的安全开发技能体系。

网络安全学习资料分享

为了帮助大家更好的学习网络安全,我把我从一线互联网大厂薅来的网络安全教程及资料分享给大家,里面的内容都是适合零基础小白的笔记和资料,不懂编程也能听懂、看懂,朋友们如果有需要这套网络安全教程+进阶学习资源包,可以扫码下方二维码限时免费领取(如遇扫码问题,可以在评论区留言领取哦)~

在这里插入图片描述

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐