SpringBoot如何使用过滤器进行XSS防御

 更新时间:2024年11月15日 15:54:35   作者:morris131  
想对全局的请求都进行XSS防御可以使用servlet中的过滤器或者spring mvc中的拦截器,下面我们就来看看如何使用servlet中的过滤器进行XSS防御吧

在Spring Boot中,我们可以使用注解的方式来进行XSS防御。注解是一种轻量级的防御手段,它可以在方法或字段级别对输入进行校验,从而防止XSS攻击。

而想对全局的请求都进行XSS防御可以使用servlet中的过滤器或者spring mvc中的拦截器,这里使用servlet中的过滤器进行演示。

引入相关依赖

maven依赖:

<!--JSR-303/JSR-380用于验证的注解 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.6.7</version>
</dependency>

如果是使用grade,引入依赖:

implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.7'

修改配置文件

xss:
  enabled: true
  excludeUrlList:
    - /xss/local/test

定义配置文件对应的属性类

package com.morris.spring.boot.module.xss;

import lombok.Data;

import java.util.List;

@Data
public class XssFilterProperties {
    /**
     * 是否启用XSS过滤。
     */
    private boolean enabled = true;

    /**
     * 需要排除的URL模式,这些URL不会进行XSS过滤。
     */
    private List<String> excludeUrlList;
}

注入XSS配置类

package com.morris.spring.boot.module.xss;

import lombok.Data;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import javax.servlet.DispatcherType;

@Data
@Configuration
public class XssFilterConfig {

    @ConfigurationProperties(prefix = "xss")
    @Bean
    public XssFilterProperties xssFilterProperties() {
        return new XssFilterProperties();
    }

    /**
     * 注册XSS过滤器。
     *
     * @return FilterRegistrationBean 用于注册过滤器的bean。
     */
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilterRegistration(XssFilterProperties xssFilterProperties) {
        FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
        // 设置过滤器的分发类型为请求类型
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        // 创建XssFilter的实例
        registrationBean.setFilter(new XssFilter(xssFilterProperties));
        // 添加过滤器需要拦截的URL模式,这里拦截所有请求
        registrationBean.addUrlPatterns("/*");
        // 设置过滤器的名称
        registrationBean.setName("XssFilter");
        // 设置过滤器的执行顺序,数值越小,优先级越高
        registrationBean.setOrder(9999);
        return registrationBean;
    }

    @Bean
    public HttpMessageConverters xssHttpMessageConverters() {
        XSSMappingJackson2HttpMessageConverter xssMappingJackson2HttpMessageConverter = new XSSMappingJackson2HttpMessageConverter();
        HttpMessageConverter converter = xssMappingJackson2HttpMessageConverter;
        return new HttpMessageConverters(converter);
    }

}

XssFilter过滤器

XssFilter过滤器会将所有需要进行防御的请求包装为XssWrapper。

package com.morris.spring.boot.module.xss;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public class XssFilter implements Filter {

    private final XssFilterProperties xssFilterProperties;

    public XssFilter(XssFilterProperties xssFilterProperties) {
        this.xssFilterProperties = xssFilterProperties;
    }

    /**
     * 执行过滤逻辑,如果当前请求不在排除列表中,则通过XSS过滤器包装请求。
     *
     * @param request  HTTP请求对象。
     * @param response HTTP响应对象。
     * @param chain    过滤器链对象,用于继续或中断请求处理。
     * @throws IOException      如果处理过程中出现I/O错误。
     * @throws ServletException 如果处理过程中出现Servlet相关错误。
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        //如果该访问接口在排除列表里面则不拦截
        if (isExcludeUrl(req.getServletPath())) {
            chain.doFilter(request, response);
            return;
        }

        log.info("uri:{}", req.getRequestURI());
        // xss 过滤
        chain.doFilter(new XssWrapper(req), resp);
    }

    /**
     * 判断当前请求的URL是否应该被排除在XSS过滤之外。
     *
     * @param urlPath 请求的URL路径。
     * @return 如果请求应该被排除,则返回true;否则返回false。
     */
    private boolean isExcludeUrl(String urlPath) {
        if (!xssFilterProperties.isEnabled()) {
            //如果xss开关关闭了,则所有url都不拦截
            return true;
        }

        if(CollectionUtils.isEmpty(xssFilterProperties.getExcludeUrlList())) {
            return false;
        }

        for (String pattern : xssFilterProperties.getExcludeUrlList()) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(urlPath);
            if (m.find()) {
                return true;
            }
        }
        return false;
    }
}

XssWrapper过滤get请求和请求头

XssWrapper会过滤get请求和请求头中的非法字符。

package com.morris.spring.boot.module.xss;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

@Slf4j
public class XssWrapper extends HttpServletRequestWrapper {

    public XssWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * 对数组参数进行特殊字符过滤
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = XssUtil.clean(values[i]);
        }
        return encodedValues;
    }

    /**
     * 对参数中特殊字符进行过滤
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (StringUtils.isBlank(value)) {
            return value;
        }
        return XssUtil.clean(value);
    }

    /**
     * 获取attribute,特殊字符过滤
     */
    @Override
    public Object getAttribute(String name) {
        Object value = super.getAttribute(name);
        if (value instanceof String && StringUtils.isNotBlank((String) value)) {
            return XssUtil.clean((String) value);
        }
        return value;
    }

    /**
     * 对请求头部进行特殊字符过滤
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (StringUtils.isBlank(value)) {
            return value;
        }
        return XssUtil.clean(value);
    }
}

MessageConverter过滤post请求

package com.morris.spring.boot.module.xss;

import com.fasterxml.jackson.databind.JavaType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.IOException;
import java.lang.reflect.Type;

/**
 * 在读取和写入JSON数据时特殊字符避免xss攻击的消息解析器
 *
 */
public class XSSMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    /**
     * 从HTTP输入消息中读取对象,同时应用XSS防护。
     * 
     * @param type        类型令牌,表示要读取的对象类型。
     * @param contextClass    上下文类,提供类型解析的上下文信息。
     * @param inputMessage HTTP输入消息,包含要读取的JSON数据。
     * @return 从输入消息中解析出的对象,经过XSS防护处理。
     * @throws IOException 如果发生I/O错误。
     * @throws HttpMessageNotReadableException 如果消息无法读取。
     */
    @Override
    public Object read(Type type, Class contextClass,
                       HttpInputMessage inputMessage) throws IOException,
            HttpMessageNotReadableException {
        JavaType javaType = getJavaType(type, contextClass);
        Object obj = readJavaType(javaType, inputMessage);
        //得到请求json
        String json = super.getObjectMapper().writeValueAsString(obj);
        //过滤特殊字符
        String result = XssUtil.clean(json);
        Object resultObj = super.getObjectMapper().readValue(result, javaType);
        return resultObj;
    }

    /**
     * 从HTTP输入消息中读取指定Java类型的对象,内部使用。
     * 
     * @param javaType    要读取的对象的Java类型。
     * @param inputMessage HTTP输入消息,包含要读取的JSON数据。
     * @return 从输入消息中解析出的对象。
     * @throws IOException 如果发生I/O错误。
     * @throws HttpMessageNotReadableException 如果消息无法读取。
     */
    private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
        try {
            return super.getObjectMapper().readValue(inputMessage.getBody(), javaType);
        } catch (IOException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }

    /**
     * 将对象写入HTTP输出消息,同时应用XSS防护。
     * 
     * @param object 要写入的对象。
     * @param outputMessage HTTP输出消息,对象将被序列化为JSON并写入此消息。
     * @throws IOException 如果发生I/O错误。
     * @throws HttpMessageNotWritableException 如果消息无法写入。
     */
    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        //得到要输出的json
        String json = super.getObjectMapper().writeValueAsString(object);
        //过滤特殊字符
        String result = XssUtil.clean(json);
        // 输出
        outputMessage.getBody().write(result.getBytes());
    }
}

Xss过滤工具类

package com.morris.spring.boot.module.xss;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;

/**
 * XSS过滤工具类,使用Jsoup库对输入的字符串进行XSS攻击防护
 */
public class XssUtil {

    /**
     * 使用jsoup自带的relaxed白名单
     */
    private static final Whitelist WHITE_LIST = Whitelist.relaxed();
    /**
     * 定义输出设置,关闭prettyPrint(prettyPrint=false),目的是避免在清理过程中对代码进行格式化
     * 从而保持输入和输出内容的一致性。
     */
    private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false);

    /*
      初始化白名单策略,允许所有标签拥有style属性。
      这是因为在富文本编辑中,样式通常通过style属性来定义,需要确保这些样式能够被保留。
     */
    static {
        // 富文本编辑时一些样式是使用 style 来进行实现的
        // 比如红色字体 style="color:red;"
        // 所以需要给所有标签添加 style 属性
        WHITE_LIST.addAttributes(":all", "style");
    }

    /**
     * 清理输入的字符串,移除潜在的XSS攻击代码。
     *
     * @param content 待清理的字符串,通常是用户输入的HTML内容。
     * @return 清理后的字符串,保证不包含XSS攻击代码。
     */
    public static String clean(String content) {
        // 使用定义好的白名单策略和输出设置清理输入的字符串
        return Jsoup.clean(content, "", WHITE_LIST, OUTPUT_SETTINGS);
    }
}

get请求测试

package com.morris.spring.boot.module.xss;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * Xss2防御get请求
 */
@RestController
@RequestMapping("/xss/global")
@Validated
public class XssGlobalGetController {

    /**
     * 使用注解拦截get请求中的xss,在方法参数前面加上@Xss,注意类上面要加上@Validated注解
     *
     * @param userAccount 请求参数
     * @return 请求参数
     */
    @GetMapping("/test")
    public String test(String userAccount) {
        return userAccount;
    }

}

发送get请求:http://localhost:8888/xss/global/test?userAccount=demoData

返回结果:demoData

post请求测试

package com.morris.spring.boot.module.xss;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Xss全局防御post请求
 */
@RestController
@RequestMapping("/xss/global")
public class XssGlobalPostController {

    /**
     * 使用注解拦截POST请求中的xss,在实体类需要拦截xss的属性上面加上@Xss或者@Validated注解
     *
     * @param userGlobalLoginPojo 实体类
     * @return 实体类
     */
    @PostMapping("/test")
    public UserGlobalLoginPojo test(@RequestBody UserGlobalLoginPojo userGlobalLoginPojo) {
        return userGlobalLoginPojo;
    }

}

发送post请求:http://localhost:8888/xss/global/test

请求体:

{
    "userAccount": "<iframe οnlοad='alert(0)'>demoData</iframe>"
}

返回结果:

{
    "userAccount": "demoData"
}

到此这篇关于SpringBoot如何使用过滤器进行XSS防御的文章就介绍到这了,更多相关SpringBoot XSS防御内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java编程中使用JDBC API连接数据库和创建程序的方法

    Java编程中使用JDBC API连接数据库和创建程序的方法

    这篇文章主要介绍了Java编程中使用JDBC API连接数据库和创建程序的基本教程,JDBC是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问需要的朋友可以参考下
    2015-12-12
  • Java IO文件编码转换实现代码

    Java IO文件编码转换实现代码

    这篇文章主要介绍了Java IO文件编码转换实现代码,有需要的朋友可以参考一下
    2013-12-12
  • Hibernate Validation自定义注解校验的实现

    Hibernate Validation自定义注解校验的实现

    这篇文章主要介绍了Hibernate Validation自定义注解校验的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • Mybatis 数据库连接池的实现示例

    Mybatis 数据库连接池的实现示例

    在Java应用程序中,与数据库的连接是非常昂贵的,因此,当我们使用MyBatis进行数据操作时,需要一个连接池来分配并管理这些连接,本文主要介绍了Mybatis 数据库连接池的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • Java中@Autowired和@Resource区别

    Java中@Autowired和@Resource区别

    本文主要介绍了Java中@Autowired和@Resource区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • 深入浅析SPI机制在JDK与Spring Boot中的应用

    深入浅析SPI机制在JDK与Spring Boot中的应用

    SPI是一种使软件框架或库更加模块化、可扩展和可维护的有效方法。通过遵循“开闭原则”, SPI 确保了系统的稳定性和灵活性,从而满足了不断变化的业务需求,这篇文章主要介绍了SPI机制在JDK与Spring Boot中的应用,需要的朋友可以参考下
    2023-09-09
  • mybatis-plus如何使用mapper的xml

    mybatis-plus如何使用mapper的xml

    这篇文章主要介绍了mybatis-plus如何使用mapper的xml问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • Java中浅拷贝和深拷贝详解

    Java中浅拷贝和深拷贝详解

    大家好,本篇文章主要讲的是Java中浅拷贝和深拷贝详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • Java 实现多线程的几种方式汇总

    Java 实现多线程的几种方式汇总

    JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
    2016-03-03
  • 举例讲解Java的Hibernate框架中的多对一和一对多映射

    举例讲解Java的Hibernate框架中的多对一和一对多映射

    这篇文章主要介绍了Java的Hibernate框架中的多对一和一对多映射,Hibernate是Java的SSH三大web开发框架之一,需要的朋友可以参考下
    2015-12-12

最新评论