配置xss过滤器相关信息
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
注入全局过滤器位于package com.ruoyi.framework.config;
包下
/**
* Filter配置
*
* @author ruoyi
*/
@Configuration
public class FilterConfig
{
@Value("${xss.excludes}")
private String excludes;
@Value("${xss.urlPatterns}")
private String urlPatterns;
/**
* 注册xss过滤器
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" }) // 忽略警告类型
@Bean
// xss.enabled=true则注入spring
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
public FilterRegistrationBean xssFilterRegistration()
{
// 创建过滤器注册对象
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
// 添加xss过滤器
registration.setFilter(new XssFilter());
// 设置过滤器过滤路径
registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
// 设置过滤器名字
registration.setName("xssFilter");
// 设置过滤器优先级,越小越优先
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
// 添加自定义参数,排除路径
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("excludes", excludes);
registration.setInitParameters(initParameters);
return registration;
}
/**
* 注入InputStream过滤器,解决InputStream只能读取一次的问题
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" }) // 忽略警告类型
@Bean
public FilterRegistrationBean someFilterRegistration()
{
// 创建过滤器注册对象
FilterRegistrationBean registration = new FilterRegistrationBean();
// 添加registration过滤器
registration.setFilter(new RepeatableFilter());
// 设置过滤器过滤路径
registration.addUrlPatterns("/*");
// 设置过滤器名
registration.setName("repeatableFilter");
// 设置过滤器优先级,越小越优先
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}
防止xss攻击位于package com.ruoyi.common.filter;
包下
/**
* 防止XSS攻击的过滤器
*
* @author ruoyi
*/
public class XssFilter implements Filter
{
/**
* 排除链接
*/
public List<String> excludes = new ArrayList<>();
/**
* 初始化,获取需要排除的链接
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
// 获取自定义参数excludes
String tempExcludes = filterConfig.getInitParameter("excludes");
// 判断是否为空
if (StringUtils.isNotEmpty(tempExcludes))
{
// 根据,分割为字符串数组
String[] url = tempExcludes.split(",");
// 遍历
for (int i = 0; url != null && i < url.length; i++)
{
// 添加到排除列表
excludes.add(url[i]);
}
}
}
/**
* 业务处理,重写request
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
// 获取req,res
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 判断是否需要过滤
if (handleExcludeURL(req, resp))
{
chain.doFilter(request, response);
return;
}
// 包装处理后的request并放行
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
/**
* 判断当前请求否需要进行xss过滤
* @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 StringUtils.matches(url, excludes);
}
@Override
public void destroy()
{
}
}
防止xss攻击具体实现
/**
* XSS过滤处理
*
* @author ruoyi
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
{
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request)
{
super(request);
}
/**
* 重写获取请求参数值
* @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] = EscapeUtil.clean(values[i]).trim();
}
return escapesValues;
}
return super.getParameterValues(name);
}
/**
* 重写获取InputStream
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException
{
// 非json类型,直接返回
if (!isJsonRequest())
{
return super.getInputStream();
}
// 为空,直接返回,自定义工具类
String json = IOUtils.toString(super.getInputStream(), "utf-8");
if (StringUtils.isEmpty(json))
{
return super.getInputStream();
}
// xss过滤,自定义工具类
json = EscapeUtil.clean(json).trim();
byte[] jsonBytes = json.getBytes("utf-8");
final ByteArrayInputStream bis = new ByteArrayInputStream(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请求
*
* @param
*/
public boolean isJsonRequest()
{
String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
}
用于解决InputStream只能读取一次的问题,位于package com.ruoyi.common.filter;
包下
/**
* Repeatable 过滤器
*
* @author ruoyi
*/
public class RepeatableFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
ServletRequest requestWrapper = null;
// 判断是否为json请求
if (request instanceof HttpServletRequest
&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
{
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper)
{
chain.doFilter(request, response);
}
else
{
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy()
{
}
}
具体实现,讲读取的内容缓存到byte数组中。再次读取读取数组中的内容