【SpringBoot】利用过滤器举行XSS防御

[复制链接]
发表于 2025-12-25 08:44:34 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在Spring Boot中,我们可以利用注解的方式来举行XSS防御。注解是一种轻量级的防御本领,它可以在方法或字段级别对输入举行校验,从而防止XSS攻击。
而想对全局的哀求都举行XSS防御可以利用servlet中的过滤器大概spring mvc中的拦截器,这里利用servlet中的过滤器举行演示。
引入干系依靠

maven依靠:
  1. <!--JSR-303/JSR-380用于验证的注解 -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-validation</artifactId>
  5.     <version>2.6.7</version>
  6. </dependency>
复制代码
假如是利用grade,引入依靠:
  1. implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.7'
复制代码
修改设置文件
  1. xss:
  2.   enabled: true
  3.   excludeUrlList:
  4.     - /xss/local/test
复制代码
界说设置文件对应的属性类
  1. package com.morris.spring.boot.module.xss;
  2. import lombok.Data;
  3. import java.util.List;
  4. @Data
  5. public class XssFilterProperties {
  6.     /**
  7.      * 是否启用XSS过滤。
  8.      */
  9.     private boolean enabled = true;
  10.     /**
  11.      * 需要排除的URL模式,这些URL不会进行XSS过滤。
  12.      */
  13.     private List<String> excludeUrlList;
  14. }
复制代码
注入XSS设置类
  1. package com.morris.spring.boot.module.xss;
  2. import lombok.Data;
  3. import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
  4. import org.springframework.boot.context.properties.ConfigurationProperties;
  5. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.http.converter.HttpMessageConverter;
  9. import javax.servlet.DispatcherType;
  10. @Data
  11. @Configuration
  12. public class XssFilterConfig {
  13.     @ConfigurationProperties(prefix = "xss")
  14.     @Bean
  15.     public XssFilterProperties xssFilterProperties() {
  16.         return new XssFilterProperties();
  17.     }
  18.     /**
  19.      * 注册XSS过滤器。
  20.      *
  21.      * @return FilterRegistrationBean 用于注册过滤器的bean。
  22.      */
  23.     @Bean
  24.     public FilterRegistrationBean<XssFilter> xssFilterRegistration(XssFilterProperties xssFilterProperties) {
  25.         FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
  26.         // 设置过滤器的分发类型为请求类型
  27.         registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
  28.         // 创建XssFilter的实例
  29.         registrationBean.setFilter(new XssFilter(xssFilterProperties));
  30.         // 添加过滤器需要拦截的URL模式,这里拦截所有请求
  31.         registrationBean.addUrlPatterns("/*");
  32.         // 设置过滤器的名称
  33.         registrationBean.setName("XssFilter");
  34.         // 设置过滤器的执行顺序,数值越小,优先级越高
  35.         registrationBean.setOrder(9999);
  36.         return registrationBean;
  37.     }
  38.     @Bean
  39.     public HttpMessageConverters xssHttpMessageConverters() {
  40.         XSSMappingJackson2HttpMessageConverter xssMappingJackson2HttpMessageConverter = new XSSMappingJackson2HttpMessageConverter();
  41.         HttpMessageConverter converter = xssMappingJackson2HttpMessageConverter;
  42.         return new HttpMessageConverters(converter);
  43.     }
  44. }
复制代码
XssFilter过滤器

XssFilter过滤器会将全部须要举行防御的哀求包装为XssWrapper。
  1. package com.morris.spring.boot.module.xss;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.util.CollectionUtils;
  4. import javax.servlet.*;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. import java.util.regex.Matcher;
  9. import java.util.regex.Pattern;
  10. @Slf4j
  11. public class XssFilter implements Filter {
  12.     private final XssFilterProperties xssFilterProperties;
  13.     public XssFilter(XssFilterProperties xssFilterProperties) {
  14.         this.xssFilterProperties = xssFilterProperties;
  15.     }
  16.     /**
  17.      * 执行过滤逻辑,如果当前请求不在排除列表中,则通过XSS过滤器包装请求。
  18.      *
  19.      * @param request  HTTP请求对象。
  20.      * @param response HTTP响应对象。
  21.      * @param chain    过滤器链对象,用于继续或中断请求处理。
  22.      * @throws IOException      如果处理过程中出现I/O错误。
  23.      * @throws ServletException 如果处理过程中出现Servlet相关错误。
  24.      */
  25.     @Override
  26.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  27.         HttpServletRequest req = (HttpServletRequest) request;
  28.         HttpServletResponse resp = (HttpServletResponse) response;
  29.         //如果该访问接口在排除列表里面则不拦截
  30.         if (isExcludeUrl(req.getServletPath())) {
  31.             chain.doFilter(request, response);
  32.             return;
  33.         }
  34.         log.info("uri:{}", req.getRequestURI());
  35.         // xss 过滤
  36.         chain.doFilter(new XssWrapper(req), resp);
  37.     }
  38.     /**
  39.      * 判断当前请求的URL是否应该被排除在XSS过滤之外。
  40.      *
  41.      * @param urlPath 请求的URL路径。
  42.      * @return 如果请求应该被排除,则返回true;否则返回false。
  43.      */
  44.     private boolean isExcludeUrl(String urlPath) {
  45.         if (!xssFilterProperties.isEnabled()) {
  46.             //如果xss开关关闭了,则所有url都不拦截
  47.             return true;
  48.         }
  49.         if(CollectionUtils.isEmpty(xssFilterProperties.getExcludeUrlList())) {
  50.             return false;
  51.         }
  52.         for (String pattern : xssFilterProperties.getExcludeUrlList()) {
  53.             Pattern p = Pattern.compile("^" + pattern);
  54.             Matcher m = p.matcher(urlPath);
  55.             if (m.find()) {
  56.                 return true;
  57.             }
  58.         }
  59.         return false;
  60.     }
  61. }
复制代码
XssWrapper过滤get哀求和哀求头

XssWrapper会过滤get哀求和哀求头中的非法字符。
  1. package com.morris.spring.boot.module.xss;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.commons.lang3.StringUtils;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletRequestWrapper;
  6. @Slf4j
  7. public class XssWrapper extends HttpServletRequestWrapper {
  8.     public XssWrapper(HttpServletRequest request) {
  9.         super(request);
  10.     }
  11.     /**
  12.      * 对数组参数进行特殊字符过滤
  13.      */
  14.     @Override
  15.     public String[] getParameterValues(String name) {
  16.         String[] values = super.getParameterValues(name);
  17.         if (values == null) {
  18.             return null;
  19.         }
  20.         int count = values.length;
  21.         String[] encodedValues = new String[count];
  22.         for (int i = 0; i < count; i++) {
  23.             encodedValues[i] = XssUtil.clean(values[i]);
  24.         }
  25.         return encodedValues;
  26.     }
  27.     /**
  28.      * 对参数中特殊字符进行过滤
  29.      */
  30.     @Override
  31.     public String getParameter(String name) {
  32.         String value = super.getParameter(name);
  33.         if (StringUtils.isBlank(value)) {
  34.             return value;
  35.         }
  36.         return XssUtil.clean(value);
  37.     }
  38.     /**
  39.      * 获取attribute,特殊字符过滤
  40.      */
  41.     @Override
  42.     public Object getAttribute(String name) {
  43.         Object value = super.getAttribute(name);
  44.         if (value instanceof String && StringUtils.isNotBlank((String) value)) {
  45.             return XssUtil.clean((String) value);
  46.         }
  47.         return value;
  48.     }
  49.     /**
  50.      * 对请求头部进行特殊字符过滤
  51.      */
  52.     @Override
  53.     public String getHeader(String name) {
  54.         String value = super.getHeader(name);
  55.         if (StringUtils.isBlank(value)) {
  56.             return value;
  57.         }
  58.         return XssUtil.clean(value);
  59.     }
  60. }
复制代码
MessageConverter过滤post哀求
  1. package com.morris.spring.boot.module.xss;
  2. import com.fasterxml.jackson.databind.JavaType;
  3. import org.springframework.http.HttpInputMessage;
  4. import org.springframework.http.HttpOutputMessage;
  5. import org.springframework.http.converter.HttpMessageNotReadableException;
  6. import org.springframework.http.converter.HttpMessageNotWritableException;
  7. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  8. import java.io.IOException;
  9. import java.lang.reflect.Type;
  10. /**
  11. * 在读取和写入JSON数据时特殊字符避免xss攻击的消息解析器
  12. *
  13. */
  14. public class XSSMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
  15.     /**
  16.      * 从HTTP输入消息中读取对象,同时应用XSS防护。
  17.      *
  18.      * @param type        类型令牌,表示要读取的对象类型。
  19.      * @param contextClass    上下文类,提供类型解析的上下文信息。
  20.      * @param inputMessage HTTP输入消息,包含要读取的JSON数据。
  21.      * @return 从输入消息中解析出的对象,经过XSS防护处理。
  22.      * @throws IOException 如果发生I/O错误。
  23.      * @throws HttpMessageNotReadableException 如果消息无法读取。
  24.      */
  25.     @Override
  26.     public Object read(Type type, Class contextClass,
  27.                        HttpInputMessage inputMessage) throws IOException,
  28.             HttpMessageNotReadableException {
  29.         JavaType javaType = getJavaType(type, contextClass);
  30.         Object obj = readJavaType(javaType, inputMessage);
  31.         //得到请求json
  32.         String json = super.getObjectMapper().writeValueAsString(obj);
  33.         //过滤特殊字符
  34.         String result = XssUtil.clean(json);
  35.         Object resultObj = super.getObjectMapper().readValue(result, javaType);
  36.         return resultObj;
  37.     }
  38.     /**
  39.      * 从HTTP输入消息中读取指定Java类型的对象,内部使用。
  40.      *
  41.      * @param javaType    要读取的对象的Java类型。
  42.      * @param inputMessage HTTP输入消息,包含要读取的JSON数据。
  43.      * @return 从输入消息中解析出的对象。
  44.      * @throws IOException 如果发生I/O错误。
  45.      * @throws HttpMessageNotReadableException 如果消息无法读取。
  46.      */
  47.     private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
  48.         try {
  49.             return super.getObjectMapper().readValue(inputMessage.getBody(), javaType);
  50.         } catch (IOException ex) {
  51.             throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
  52.         }
  53.     }
  54.     /**
  55.      * 将对象写入HTTP输出消息,同时应用XSS防护。
  56.      *
  57.      * @param object 要写入的对象。
  58.      * @param outputMessage HTTP输出消息,对象将被序列化为JSON并写入此消息。
  59.      * @throws IOException 如果发生I/O错误。
  60.      * @throws HttpMessageNotWritableException 如果消息无法写入。
  61.      */
  62.     @Override
  63.     protected void writeInternal(Object object, HttpOutputMessage outputMessage)
  64.             throws IOException, HttpMessageNotWritableException {
  65.         //得到要输出的json
  66.         String json = super.getObjectMapper().writeValueAsString(object);
  67.         //过滤特殊字符
  68.         String result = XssUtil.clean(json);
  69.         // 输出
  70.         outputMessage.getBody().write(result.getBytes());
  71.     }
  72. }
复制代码
Xss过滤工具类
  1. package com.morris.spring.boot.module.xss;
  2. import org.jsoup.Jsoup;
  3. import org.jsoup.nodes.Document;
  4. import org.jsoup.safety.Whitelist;
  5. /**
  6. * XSS过滤工具类,使用Jsoup库对输入的字符串进行XSS攻击防护
  7. */
  8. public class XssUtil {
  9.     /**
  10.      * 使用jsoup自带的relaxed白名单
  11.      */
  12.     private static final Whitelist WHITE_LIST = Whitelist.relaxed();
  13.     /**
  14.      * 定义输出设置,关闭prettyPrint(prettyPrint=false),目的是避免在清理过程中对代码进行格式化
  15.      * 从而保持输入和输出内容的一致性。
  16.      */
  17.     private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false);
  18.     /*
  19.       初始化白名单策略,允许所有标签拥有style属性。
  20.       这是因为在富文本编辑中,样式通常通过style属性来定义,需要确保这些样式能够被保留。
  21.      */
  22.     static {
  23.         // 富文本编辑时一些样式是使用 style 来进行实现的
  24.         // 比如红色字体 style="color:red;"
  25.         // 所以需要给所有标签添加 style 属性
  26.         WHITE_LIST.addAttributes(":all", "style");
  27.     }
  28.     /**
  29.      * 清理输入的字符串,移除潜在的XSS攻击代码。
  30.      *
  31.      * @param content 待清理的字符串,通常是用户输入的HTML内容。
  32.      * @return 清理后的字符串,保证不包含XSS攻击代码。
  33.      */
  34.     public static String clean(String content) {
  35.         // 使用定义好的白名单策略和输出设置清理输入的字符串
  36.         return Jsoup.clean(content, "", WHITE_LIST, OUTPUT_SETTINGS);
  37.     }
  38. }
复制代码
get哀求测试
  1. package com.morris.spring.boot.module.xss;
  2. import org.springframework.validation.annotation.Validated;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. /**
  7. * Xss2防御get请求
  8. */
  9. @RestController
  10. @RequestMapping("/xss/global")
  11. @Validated
  12. public class XssGlobalGetController {
  13.     /**
  14.      * 使用注解拦截get请求中的xss,在方法参数前面加上@Xss,注意类上面要加上@Validated注解
  15.      *
  16.      * @param userAccount 请求参数
  17.      * @return 请求参数
  18.      */
  19.     @GetMapping("/test")
  20.     public String test(String userAccount) {
  21.         return userAccount;
  22.     }
  23. }
复制代码
发送get哀求:http://localhost:8888/xss/global/test?userAccount=demoData
返回结果:demoData
post哀求测试
  1. package com.morris.spring.boot.module.xss;
  2. import org.springframework.web.bind.annotation.PostMapping;
  3. import org.springframework.web.bind.annotation.RequestBody;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. /**
  7. * Xss全局防御post请求
  8. */
  9. @RestController
  10. @RequestMapping("/xss/global")
  11. public class XssGlobalPostController {
  12.     /**
  13.      * 使用注解拦截POST请求中的xss,在实体类需要拦截xss的属性上面加上@Xss或者@Validated注解
  14.      *
  15.      * @param userGlobalLoginPojo 实体类
  16.      * @return 实体类
  17.      */
  18.     @PostMapping("/test")
  19.     public UserGlobalLoginPojo test(@RequestBody UserGlobalLoginPojo userGlobalLoginPojo) {
  20.         return userGlobalLoginPojo;
  21.     }
  22. }
复制代码
发送post哀求:http://localhost:8888/xss/global/test
哀求体:
  1. {
  2.     "userAccount": "<iframe οnlοad='alert(0)'>demoData</iframe>"
  3. }
复制代码
返回结果:
  1. {
  2.     "userAccount": "demoData"
  3. }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表