Spring boot 添加 XssFilter过滤器(接口必须是Json入参格式)

本文详细介绍了一种用于防御XSS跨站脚本攻击及SQL注入的安全过滤器实现。通过正则表达式检查请求参数,确保应用免受恶意攻击。

第一步部分代码: XSS_ERROR(90006, “入参含有非法字符”)

@Component
@Slf4j
@WebFilter(filterName = "xssFilter", urlPatterns = "/*")
@Order(5)
public class XssFilter implements Filter {

    private static final String SCRIPT_LOW_REGEX = ".*((((\\%3C)|<)[^\\n]+((\\%3E)|>))|(((\\%22)|"|(\\%27)|')[(\\%20) ]*((\\%2B)|\\+|(\\%3B)|;))|(((\\%3D)|=)[(\\%20) ]*((\\%22)|"|(\\%27)|'))).*";
    private static final String SCRIPT_UPPER_REGEX = ".*((((\\%3C)|<)[^\\n]+((\\%3E)|>))|(((\\%22)|"|(\\%27)|')[(\\%20) ]*((\\%2B)|\\+|(\\%3B)|;))|(((\\%3D)|=)[(\\%20) ]*((\\%22)|"|(\\%27)|'))).*";
    private static final String SQL_REGEX = ".*((\\%27)|(\\')).*((\\-\\-)|(((\\%6F)|o|(\\%4F))((\\%72)|r|(\\%52)))|((\\%3B)|(;))).*";
    private static final String NOT_PROTECT = "/undefined$";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("*************执行过滤xssFilter******");
        HttpServletResponse res = (HttpServletResponse) servletResponse;
        res.setCharacterEncoding("UTF-8");
        res.setContentType("application/json; charset=utf-8");
        HttpServletRequest request ;
        if (servletRequest instanceof HttpServletRequest) {
            request = new XssHttpServletRequestWrapper((HttpServletRequest) servletRequest);
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        //是否合法 :true 合法,false 不合法
        boolean isLegal = checkParams(request) ;
        String url = request.getRequestURI();
        log.info("-----------XssFilter ---- 接口{}是否合法:{}",url,isLegal);
        final Pattern notProject = Pattern.compile(NOT_PROTECT);
        if (!notProject.matcher(url).find()) {
            if (!isLegal) {
                PrintWriter writer = null;
                try {
                    writer = res.getWriter();
                    String resultVal = JSON.toJSONString(ApiRes.getInstance(ResultEnum.XSS_ERROR));
                    writer.write(resultVal);
                    writer.flush();
                    writer.close();
                } catch (Exception e) {
                    log.error("AuthPathFilter Error" + e.getMessage(), e);
                } finally {
                    if (null != writer) {
                        writer.close();
                    }
                }
                return;
            }
        }
        filterChain.doFilter(request, servletResponse);
        return;
    }

    @Override
    public void destroy() {

    }

    /**
     * 校验入参是否含有特殊字符
     * @param request
     * @return
     */
    private boolean checkParams(HttpServletRequest request) {
        Pattern scriptLowRegex = Pattern.compile(SCRIPT_LOW_REGEX);
        Pattern sqlRegex = Pattern.compile(SQL_REGEX);
        Pattern scriptUpperRegex = Pattern.compile(SCRIPT_UPPER_REGEX);

        try (BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"))){
            StringBuilder responseStrBuilder = new StringBuilder();
            String inputStr;
            while ((inputStr = streamReader.readLine()) != null) {
                responseStrBuilder.append(inputStr);
            }
            String jString = responseStrBuilder.toString();
            //组装成数组
            if (jString.indexOf("[") ==  -1) {
                jString = "["+jString+"]";
            }
            if (StringUtils.isNotBlank(jString)) {
                boolean valid = isJSONValid(jString);
                if (valid) {
                    JSONArray jsonArray = JSONArray.parseArray(jString);
                    for (int i = 0; i < jsonArray.size() ; i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        Iterator<Map.Entry<String, Object>> iterator = jsonObject.entrySet().iterator();
                        while (iterator.hasNext()) {
                            Map.Entry<String, Object> next = iterator.next();
                            if (!Objects.isNull(next.getValue())) {
                                String value = next.getValue().toString();
                                String paraValue = value.toUpperCase();
                                paraValue = replaceStr(paraValue);
                                if (scriptLowRegex.matcher(paraValue).matches()|| sqlRegex.matcher(paraValue).matches() || scriptUpperRegex.matcher(paraValue).matches()) {
                                    return false;
                                }
                            }
                        }
                    }
                }
            }

        } catch (IOException io) {
            io.printStackTrace();
            log.error("参数解析错误", io);
        }
        return true;
    }

    /**
     * 判断字符串是否满足json格式
     * @param jsonInString
     * @return
     */
    private boolean isJSONValid(String jsonInString) {
        try {
            final ObjectMapper mapper = new ObjectMapper();
            mapper.readTree(jsonInString);
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * 转义字符
     * @param value
     * @return
     */
    private String replaceStr(String value) {
        value = value.replace("+", "%2B")
                //.replace("/", "%2F")
                .replace("?", "%3F")
                .replace("%", "%25")
                .replace("#", "%23")
                .replace("&", "%26")
                .replace("=", "%3D")
                .replace("@", "%40")
                .replace(":", "%3A")
                .replace(";", "%3B")
                .replace("<", "%3C")
                .replace(">", "%3E")
                .replace("\\", "%5C")
                .replace("|", "%7C")
                .replace("$", "%24")
                .replace("^", "%5E")
                .replace(",", "%2C")
                .replace("'", "%27")
                .replace("=", "%3D")
                .replace("[", "%5B")
                .replace("]", "%5D")
                .replace("{", "%7B")
                .replace("}", "%7D")
                .replace("\"", "%22");
        return value;
    }
}

第二部分代码:

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] bytes;

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 读取输入流里的请求参数,并保存到bytes里
        bytes = IOUtils.toByteArray(request.getInputStream());

    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new BufferedServletInputStream(this.bytes);
    }

    class BufferedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream inputStream;
        public BufferedServletInputStream(byte[] buffer) {
            //此处即赋能,可以详细查看ByteArrayInputStream的该构造函数;
            this.inputStream = new ByteArrayInputStream( buffer );
        }
        @Override
        public int available() throws IOException {
            return inputStream.available();
        }
        @Override
        public int read() throws IOException {
            return inputStream.read();
        }
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return inputStream.read( b, off, len );
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener listener) {

        }
    }
}
<think>我们正在讨论Spring Boot中配置全局XSS过滤器的问题。根据用户的问题,他想要一个全局的XSS过滤配置方案。在Spring Boot中,我们可以通过创建一个过滤器(Filter)来对请求数进行过滤,防止XSS攻击。 解决方案通常包括: 1. 编写一个XSS过滤器的类,实现javax.servlet.Filter接口。 2. 在过滤器中,对请求进行包装,重写getParameterValues, getParameter等方法,对数值进行清理。 3. 将过滤器注册为Spring Bean,并配置过滤的URL模式。 另外,我们也可以考虑使用Spring Security的XSS保护机制,但用户要求的是全局过滤器,所以我们重点放在自定义过滤器上。 注意:我们也可以Spring Security的XSS保护,但这里用户要求的是全局过滤器配置,所以我们主要演示自定义过滤器。 步骤: 1. 创建XSS过滤器类,使用HttpServletRequestWrapper来包装请求,重写获取数的方法,对数进行转义或清理。 2. 注册该过滤器,使其对所有的请求都生效。 同时,我们需要注意,在Spring Boot中注册过滤器有两种方式: a. 使用@Bean和FilterRegistrationBean b. 使用@WebFilter和@ServletComponentScan 这里我们使用第一种方式,因为更灵活,可以设置过滤顺序、URL模式等。 下面是一个示例: 首先,创建一个XSS过滤器类,它将对请求中的数进行清理(例如使用Apache Commons Text的StringEscapeUtils来转义HTML): 注意:需要添加依赖(如果使用Apache Commons Text) 在pom.xml中添加: <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.9</version> </dependency> 但是,请注意,转义可能会在某些场景下导致问题(比如富文本输),因此在实际应用中,可能需要更复杂的策略,例如使用白名单过滤(如使用OWASP Java HTML Sanitizer)或者只对特定字段进行过滤。这里我们演示一个简单的转义。 然而,考虑到用户可能不想引额外的依赖,我们也可以使用Java自带的转义方法,但Java标准库中没有提供直接的HTML转义方法。因此,我们可以自己写一个简单的转义,或者使用Spring的HtmlUtils。但是,过滤器是在Servlet层面,不依赖Spring Web,所以我们可以使用自己写的转义函数或者使用Apache Commons Text。 为了简单,我们假设使用Apache Commons Text的StringEscapeUtils.escapeHtml4()方法。 但是,如果用户不想添加依赖,我们可以自己写一个简单的转义函数,例如: public static String escapeHtml(String input) { if (input == null) { return null; } return input.replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) .replaceAll("'", "'"); } 不过,这种方式可能不够高效,而且可能不够全面。因此,我们根据用户的选择来决定。 这里为了演示,我们使用自己写的简单转义方法。 另外,我们还需要注意,对于POST请求,如果请求体是表单数据(application/x-www-form-urlencoded),我们可以通过重写getParameter等方法进行过滤。但是,如果是JSON请求(application/json),则需要在过滤器中读取请求体,过滤后再重新设置。但是,这样做会影响性能,而且需要谨慎处理,因为请求体只能读取一次。因此,我们可以使用ContentCachingRequestWrapper,但这里为了简化,我们先处理表单数据。 对于JSON数据,通常建议在反序列化之前进行过滤,或者使用AOP在Controller方法数上进行处理。全局过滤器处理JSON请求体比较复杂,这里我们只处理表单数据。 如果用户需要处理JSON请求体,可以考虑使用@ControllerAdvice和@InitBinder或者使用AOP,或者使用自定义的MessageConverter。但用户要求全局过滤器,我们可以尝试在过滤器中处理,但要注意性能。 由于时间关系,我们这里只处理表单数据(即传统的数)。对于JSON请求体,我们可以留作后续扩展。 因此,我们创建一个XSS请求包装器: 类名:XSSRequestWrapper 然后,在过滤器中,我们将请求替换为这个包装器。 下面给出代码示例: 1. 创建XSSRequestWrapper类,继承HttpServletRequestWrapper,并重写相关方法。 2. 创建XSSFilter类,实现Filter接口,在doFilter方法中将请求替换为XSSRequestWrapper。 然后,注册这个过滤器。 具体步骤: 步骤1:创建XSSRequestWrapper ``` import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.Collections; import java.util.Enumeration; import java.util.Map; import java.util.HashMap; public class XSSRequestWrapper extends HttpServletRequestWrapper { public XSSRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { String value = super.getParameter(name); return escapeHtml(value); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) { return null; } int length = values.length; String[] escapedValues = new String[length]; for (int i = 0; i < length; i++) { escapedValues[i] = escapeHtml(values[i]); } return escapedValues; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> parameterMap = super.getParameterMap(); Map<String, String[]> escapedMap = new HashMap<>(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String[] escapedValues = new String[entry.getValue().length]; for (int i = 0; i < entry.getValue().length; i++) { escapedValues[i] = escapeHtml(entry.getValue()[i]); } escapedMap.put(entry.getKey(), escapedValues); } return escapedMap; } @Override public String getHeader(String name) { String value = super.getHeader(name); return escapeHtml(value); } private static String escapeHtml(String input) { if (input == null) { return null; } return input.replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) .replaceAll("'", "'"); } } ``` 步骤2:创建XSSFilter ``` import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class XSSFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化 } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 将请求包装 chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response); } @Override public void destroy() { // 销毁 } } ``` 步骤3:注册过滤器Spring Boot的配置类中(比如主类或者一个专门的配置类)注册这个过滤器: ``` import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<XSSFilter> xssFilterRegistration() { FilterRegistrationBean<XSSFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new XSSFilter()); registration.addUrlPatterns("/*"); // 过滤所有请求 registration.setName("xssFilter"); registration.setOrder(1); // 设置过滤器顺序 return registration; } } ``` 这样,我们就完成了一个简单的XSS全局过滤器。 但是,请注意: 1. 这个过滤器只对表单提交的数和头信息进行了转义,对于JSON请求体无效。 2. 转义可能会对富文本内容造成影响,因此在实际应用中,可能需要对某些路径或某些数进行排除,或者使用白名单策略。 另外,我们也可以使用Spring Security的XSS保护,它通过设置HTTP头来启用浏览器的XSS保护机制。例如: ``` @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers() .xssProtection() .block(false); // 启用XSS保护,但设置为不阻止,而是过滤(根据需求设置) } } ``` 但是,这种方式主要是设置HTTP响应头,如`X-XSS-Protection`,它依赖于浏览器的XSS保护机制,并不能完全替代服务器端的过滤。 因此,我们通常建议结合使用:服务器端过滤和设置安全响应头。 根据用户需求,他要求的是全局过滤器配置,所以我们提供了自定义过滤器的方案。 相关问题: 1. 如何使XSS过滤器只对特定的URL生效? 2. 如何处理JSON请求体中的XSS攻击? 3. 在Spring Boot中,过滤器的执行顺序是如何控制的? 注意:上面的转义函数只是一个简单示例,实际生产环境中可能需要更健壮的转义库,比如使用OWASP Java Encoder或者commons-text等。 另外,如果用户不想添加依赖,我们可以自己写一个更完整的转义函数,或者使用Spring Web提供的HtmlUtils(但需要引Spring Web,并且过滤器不依赖Spring上下文)。在我们的过滤器中,由于是独立的,所以没有使用HtmlUtils。 如果用户已经在项目中使用了Spring Web,也可以考虑在过滤器中引HtmlUtils,但这样会耦合。因此,我们这里选择了自己写一个简单的转义函数。 用户可以根据实际需求调整转义的逻辑。 引用: 在Spring Security中配置XSS保护头,如引用[1]所示,可以通过.headers().xssProtection()来配置。但请注意,引用[1]中的配置是用于设置HTTP响应头的,而我们的自定义过滤器是用于请求数的过滤。两者可以同时使用。 因此,我们可以同时配置自定义过滤器Spring Security的XSS保护头,以提供双重保护。 最后,我们生成相关问题:</think>### Spring Boot全局XSS过滤器配置指南 在Spring Boot中实现全局XSS防护,推荐通过自定义过滤器拦截请求数进行过滤。以下是完整解决方案: #### 1. 创建XSS过滤器类 ```java import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class XSSFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 包装原始请求进行XSS过滤 chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response); } } ``` #### 2. 创建请求包装器 ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.text.StringEscapeUtils; public class XSSRequestWrapper extends HttpServletRequestWrapper { public XSSRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { return escapeXSS(super.getParameter(name)); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) return null; return Arrays.stream(values) .map(this::escapeXSS) .toArray(String[]::new); } private String escapeXSS(String value) { if (value == null) return null; // 使用Apache Commons Text进行HTML转义 return StringEscapeUtils.escapeHtml4(value); } } ``` #### 3. 注册全局过滤器 ```java import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<XSSFilter> xssFilter() { FilterRegistrationBean<XSSFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new XSSFilter()); registration.addUrlPatterns("/*"); // 过滤所有URL registration.setOrder(1); // 设置最高优先级 registration.setName("xssFilter"); return registration; } } ``` #### 4. 添加依赖 (pom.xml) ```xml <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.9</version> </dependency> ``` #### 关键说明: 1. **过滤范围**:覆盖所有请求数(`getParameter`和`getParameterValues`) 2. **转义规则**: - `<` → `<` - `>` → `>` - `&` → `&` - `"` → `"` 3. **执行顺序**:通过`setOrder(1)`确保最先执行 4. **安全增强**:建议同时启用Spring Security的CSP保护[^1]: ```java .headers() .contentSecurityPolicy("script-src 'self'") ``` #### 注意事项: - 富文本内容需特殊处理(使用白名单过滤库如JSoup) - 对JSON请求需在消息转换器层额外处理 - 结合日志监控过滤操作,避免过度转义 - 推荐使用OWASP Java Encoder库增强防护 > 示例过滤效果: > 输:`<script>alert(1)</script>` > 输出:`<script>alert(1)</script>`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值