文章目录
- 前言
- 一、使用注解预防
- 1. 添加依赖
- 2. 自定义注解
- 3. 自定义校验逻辑
- 4. 使用
- 二、使用过滤器
- 1. 添加配置
- 2. 创建配置类
- 3. 创建过滤器
- 4. 创建过滤器类
- 5. 使用
前言
xss攻击时安全领域中非常常见的一种方法,保证我们的系统安全是非常重要的
xss攻击简单来说就是在用户输入内容中添加脚本< script >…< script >
这里面可能包含获取cookie,
一、使用注解预防
1. 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Constraint(validatedBy = {XssValidator.class})
public @interface Xss {String message() default "不允许任何脚本运行";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
3. 自定义校验逻辑
public class XssValidator implements ConstraintValidator<Xss, String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {// 这里用的hutool的工具类return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);}}
4. 使用
创建实体类
@Data
public class Book {private Long id;private String name;@Xssprivate String content;
}
创建book控制器
@Validated
@RestController
@RequestMapping("/book")
public class BookController {@PostMappingpublic void save(@Validated @RequestBody Book book){System.out.println(book);}
}
发送请求
可以看到系统抛出了异常,这样我们就成功了使用注解完成了脚本验证
二、使用过滤器
注解的方式需要一个一个的添加,这显然是不太方便的。我们可以通过过滤器的方式对前端传递过来的参数进行统一处理
1. 添加配置
# 防止XSS攻击
xss:# 过滤开关enabled: true# 排除链接(多个用逗号分隔)excludes: # 匹配链接urlPatterns: /book/*
2. 创建配置类
@Data
@Component
@ConfigurationProperties(prefix = "xss")
public class XssProperties {/*** 过滤开关*/private String enabled;/*** 排除链接(多个用逗号分隔)*/private String excludes;/*** 匹配链接*/private String urlPatterns;}
3. 创建过滤器
@Configuration
public class FilterConfig {@Autowiredprivate XssProperties xssProperties;// 关闭校验注解@SuppressWarnings({"rawtypes", "unchecked"})@Bean// xss.enabled==true时,注入bean@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public FilterRegistrationBean xssFilterRegistration() {// 创建过滤器注册器FilterRegistrationBean registration = new FilterRegistrationBean();// 设置运行类型registration.setDispatcherTypes(DispatcherType.REQUEST);// 设置过滤器registration.setFilter(new XssFilter());// 添加拦截路径registration.addUrlPatterns(StrUtil.split(xssProperties.getUrlPatterns(), StrUtil.C_COMMA).toArray(String[]::new));registration.setName("xssFilter");// 设置优先级为最高registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);// 添加自定义参数Map<String, String> initParameters = new HashMap<String, String>();initParameters.put("excludes", xssProperties.getExcludes());registration.setInitParameters(initParameters);return registration;}
}
4. 创建过滤器类
public class XssFilter implements Filter {/*** 排除链接*/public List<String> excludes = new ArrayList<>();/*** 初始化的时候将排除连接根据,分割添加到excludes中* @param filterConfig* @throws ServletException*/@Overridepublic void init(FilterConfig filterConfig) throws ServletException {String tempExcludes = filterConfig.getInitParameter("excludes");if (StrUtil.isNotBlank(tempExcludes)) {String[] url = tempExcludes.split(StrUtil.COMMA);excludes.addAll(Arrays.asList(url));}}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;if (handleExcludeURL(req, resp)) {chain.doFilter(request, response);return;}XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);chain.doFilter(xssRequest, response);}/*** 判断是否为排除过滤路径* @param request* @param response* @return*/private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {String url = request.getServletPath();String method = request.getMethod();// GET DELETE 不过滤if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) {return true;}return matches(url, excludes);}public static boolean matches(String str, List<String> strs) {if (StrUtil.isBlank(str) || CollUtil.isEmpty(strs)) {return false;}for (String pattern : strs) {if (isMatch(pattern, str)) {return true;}}return false;}public static boolean isMatch(String pattern, String url) {AntPathMatcher matcher = new AntPathMatcher();return matcher.match(pattern, url);}@Overridepublic void destroy() {}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {/*** @param request*/public XssHttpServletRequestWrapper(HttpServletRequest request) {super(request);}/*** 将url拼接的参数进行脚本过滤* @param name* @return*/@Overridepublic String[] getParameterValues(String name) {String[] values = super.getParameterValues(name);if (values != null) {int length = values.length;String[] escapesValues = new String[length];for (int i = 0; i < length; i++) {// 防xss攻击和过滤前后空格escapesValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();}return escapesValues;}return super.getParameterValues(name);}/*** 对body的脚本参数进行过滤* @return* @throws IOException*/@Overridepublic ServletInputStream getInputStream() throws IOException {// 非json类型,直接返回if (!isJsonRequest()) {return super.getInputStream();}// 为空,直接返回String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8);if (StringUtils.isEmpty(json)) {return super.getInputStream();}// xss过滤json = HtmlUtil.cleanHtmlTag(json).trim();byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes);return new ServletInputStream() {@Overridepublic boolean isFinished() {return true;}@Overridepublic boolean isReady() {return true;}@Overridepublic int available() throws IOException {return jsonBytes.length;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() throws IOException {return bis.read();}};}/*** 是否是Json请求*/public boolean isJsonRequest() {String header = super.getHeader(HttpHeaders.CONTENT_TYPE);return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);}
}
5. 使用
发送请求
可以看到传递的脚本被成功过滤掉