文章目录
- 前言
- 一、使用注解预防
- 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> {
@Override
public 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;
@Xss
private String content;
}
创建book控制器
@Validated
@RestController
@RequestMapping("/book")
public class BookController {
@PostMapping
public 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 {
@Autowired
private 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
*/
@Override
public 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));
}
}
@Override
public 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);
}
@Override
public void destroy() {
}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* 将url拼接的参数进行脚本过滤
* @param name
* @return
*/
@Override
public 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
*/
@Override
public 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() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public int available() throws IOException {
return jsonBytes.length;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public 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. 使用
发送请求
可以看到传递的脚本被成功过滤掉