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.2HTML 富文本过滤(开源库)类似 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 垂直技术社区,欢迎活跃、内容共建。

更多推荐