1. web.xml 配置过滤器
<!-- SQL、JS脚本 注入攻击过滤器 -->
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.huawei.esysadmin.common.filter.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2. XssFilter 代码实现
public class XssFilter extends BaseFilter implements Filter
{
private static final Log logger = LogFactory.getLog(XssFilter.class);
/**
* 必须要过滤的URL
*/
private String[] mustFilterUrlList = null;
/**
* 不进行过滤的URL
*/
private String[] notFilterUrlList = null;
/**
* 需要过滤的关键词
*/
private String[] filterWordList = null;
/**
* 包含非法单词,但是整体合法的单词
*/
private String[] ignorWordList = null;
/**
* 过滤器入口方法
*
*/
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest)arg0;
HttpServletResponse response = (HttpServletResponse)arg1;
String url = request.getRequestURI();
if (this.ignoreUrlFromWeb(url.replaceAll(request.getContextPath(), "")))
{
arg2.doFilter(request, response);
return;
}
// 检查url是否必须要过滤掉
if (this.isUrlMustFilter(url))
{
logger.info("XssFilter filtered invalid url \"" + url + "\", and will response 404 to client.");
response.setStatus(404);
return;
}
// 检查url是否不需要过滤
if (this.isUrlNotFilter(url))
{
// 如果不需要过滤,则执行下一个过滤器
arg2.doFilter(arg0, arg1);
return;
}
//校验queryString
String queryString = request.getQueryString();
if (containsIllegalWord(queryString))
{
logger.info("XssFilter filtered invalid queryString \"" + queryString + "\", and will redirect to error page.");
throw new ESysAdminException("invalid request");
}
// 解析请求参数,并进行参数过滤
Enumeration<String> params = request.getParameterNames();
if (null != params)
{
// 参数名称
String paramName = null;
// 参数值
String[] paramValues = null;
while (params.hasMoreElements())
{
paramName = params.nextElement();
paramValues = request.getParameterValues(paramName);
if (null == paramValues)
{
continue;
}
for (String value : paramValues)
{
if (this.containsIllegalWord(value))
{
logger.info("XssFilter filtered invalid input param \"" + paramName + "\" with values \"" + value + "\", and will redirect to error page.");
throw new ESysAdminException("invalid request");
}
}
}
}
String type = request.getContentType();
// 文件上传类型采用的contenttype为multipart/form-data;类型,上传的是文件流,无须校验
// 如果是普通的form表单提交的,contenttype是非json模式的,不走json获取的参数校验
if (StringUtils.isBlank(type) || !type.startsWith("application/json"))
{
arg2.doFilter(request, arg1);
return;
}
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest)
{
requestWrapper = new ReaderReuseHttpServletRequestWrapper(request);
}
Reader reader = requestWrapper.getReader();
// 读取Request Payload数据 axios的请求类型
String payload = IOUtils.toString(reader);
if (StringUtils.isNotBlank(type) && type.startsWith("application/json"))
{
parseJSONString(payload,request,response);
}
else
{
// 解析请求参数,并进行参数过滤
Enumeration<String> params1 = request.getParameterNames();
if (null != params1)
{
// 参数名称
String paramName = null;
// 参数值
String[] paramValues = null;
while (params1.hasMoreElements())
{
paramName = params1.nextElement();
paramValues = request.getParameterValues(paramName);
if (null == paramValues)
{
continue;
}
for (String value : paramValues)
{
if (this.containsIllegalWord(value))
{
logger.error("XssFilter filtered invalid input param \"" + paramName + "\" with values \""
+ value + "\", and will redirect to error page.");
throw new ESysAdminException("invalid request");
}
}
}
}
}
arg2.doFilter(requestWrapper, arg1);
}
/**
* 判断url是否必须过滤
*
* @param url
* @return
* @see [类、类#方法、类#成员]
*/
private boolean isUrlMustFilter(String url)
{
// 查看参数表中是否存在需要过滤的url配置
//String[] mustFilterUrls = getXssFilterConfigParam("BBCYYGLPT_XSSFILTER_MUSTFILTERURL");
String[] mustFilterUrls = null;
ConfBlackWhiteListBean mustFilterUrlsConfig = ParamDictUtil.queryBlackWhiteList("black", "BBCYYGLPT_XSSFILTER_MUSTFILTERURL");
if (null != mustFilterUrlsConfig)
{
List<String> mustFilterList = mustFilterUrlsConfig.getBlackWhiteList();
if (null != mustFilterList && !mustFilterList.isEmpty())
{
mustFilterUrls = mustFilterList.stream()
.map(word -> word.toLowerCase(Locale.US))
.collect(Collectors.toList())
.toArray(new String[mustFilterList.size()]);
}
}
if (null != mustFilterUrls)
{
mustFilterUrlList = mustFilterUrls;
}
// url为空或未配置必须过滤的url,则不是必须过滤的url
if (StringUtils.isBlank(url) || null == mustFilterUrlList || 0 == mustFilterUrlList.length)
{
return false;
}
// 转换为小写字符
url = url.toLowerCase(Locale.US);
for (String must : mustFilterUrlList)
{
if (url.contains(must))
{
return true;
}
}
return false;
}
/**
* 判断url是否不需要过滤
*
* @param url
* @return
* @see [类、类#方法、类#成员]
*/
private boolean isUrlNotFilter(String url)
{
// url为空,则不需要过滤
if (StringUtils.isBlank(url))
{
return true;
}
// 转换为小写字符
url = url.toLowerCase(Locale.US);
// 查看参数表中是否存在不需要过滤的url配置
//String[] notFilterUrls = getXssFilterConfigParam("BBCYYGLPT_XSSFILTER_NOTFILTERURL");
String[] notFilterUrls = null;
ConfBlackWhiteListBean queryBlackWhiteListConfig = ParamDictUtil.queryBlackWhiteList("white",
"BBCYYGLPT_XSSFILTER_NOTFILTERURL");
if (null != queryBlackWhiteListConfig)
{
List<String> blackWhiteList = queryBlackWhiteListConfig.getBlackWhiteList();
if (null != blackWhiteList && !blackWhiteList.isEmpty())
{
notFilterUrls = blackWhiteList.stream()
.map(urlWhite -> urlWhite.toLowerCase(Locale.US))
.collect(Collectors.toList())
.toArray(new String[blackWhiteList.size()]);
}
}
if (null != notFilterUrls)
{
notFilterUrlList = notFilterUrls;
}
// 判断url是否不需要过滤
if (null != notFilterUrlList)
{
for (String not : notFilterUrlList)
{
if (url.contains(not))
{
return true;
}
}
}
return false;
}
/**
* 判断字符串是否包含非法关键字
*
* @param s
* @return
* @see [类、类#方法、类#成员]
*/
private boolean containsIllegalWord(String s)
{
// 参数值为空,默认合法
if (StringUtils.isBlank(s))
{
return false;
}
// 转换为小写模式
s = s.toLowerCase(Locale.US);
// 查看参数表中是否存在忽略关键词配置
//String[] ignorWords = getXssFilterConfigParam("BBCYYGLPT_XSSFILTER_IGNORWORDS");
String[] ignorWords = null;
ConfBlackWhiteListBean ignorWordsConfig = ParamDictUtil.queryBlackWhiteList("white",
"BBCYYGLPT_XSSFILTER_IGNORWORDS");
if (null != ignorWordsConfig)
{
List<String> ignorWordWhiteList = ignorWordsConfig.getBlackWhiteList();
if (null != ignorWordWhiteList && !ignorWordWhiteList.isEmpty())
{
ignorWords = ignorWordWhiteList.stream()
.map(ignorWord -> ignorWord.toLowerCase(Locale.US))
.collect(Collectors.toList())
.toArray(new String[ignorWordWhiteList.size()]);
}
}
if (null != ignorWords)
{
ignorWordList = ignorWords;
}
// 排除忽略关键词
if (null != ignorWordList)
{
for (String ignor : ignorWordList)
{
s = s.replaceAll(ignor, "");
}
}
// 查看参数表中是否存在非法关键词配置
//String[] filterWords = getXssFilterConfigParam("BBCYYGLPT_XSSFILTER_FILTERWORDS");
String[] filterWords = null;
ConfBlackWhiteListBean filterWordsConfig = ParamDictUtil.queryBlackWhiteList("black", "BBCYYGLPT_XSSFILTER_FILTERWORDS");
if (null != filterWordsConfig)
{
List<String> result = filterWordsConfig.getBlackWhiteList();
if (null != result && !result.isEmpty())
{
filterWords = result.stream()
.map(word -> word.toLowerCase(Locale.US))
.collect(Collectors.toList())
.toArray(new String[result.size()]);
}
}
if (null != filterWords)
{
filterWordList = filterWords;
}
// 过滤非法关键词
if (null != filterWordList)
{
for (String filter : filterWordList)
{
if (s.contains(filter))
{
return true;
}
}
}
//ParamBean config = CacheUtil.getParameterBean("BBCYYGLPT_XSSFILTER_REGEXP", Constants.PARAM_REGION_ALL);
ConfParameterBean config = ParamDictUtil.queryParameterById(ParamDictConstants.ParamType.RULE, "BBCYYGLPT_XSSFILTER_REGEXP", ParamDictConstants.BusiType.SYSTEM);
if (null != config && StringUtils.isNotBlank(config.getParamValue()))
{
Pattern pattern = Pattern.compile(config.getParamValue());
if (pattern.matcher(s).find())
{
return true;
}
}
return false;
}
/**
* 从参数表中读取XssFilter的参数配置
*
* @param paramid
* @return
* @see [类、类#方法、类#成员]
*/
private String[] getXssFilterConfigParam(String paramid)
{
ParamBean config = CacheUtil.getParameterBean(paramid, Constants.PARAM_REGION_ALL);
if (null != config && null != config.getParavalue())
{
String[] words = config.getParavalue().replaceAll(",", ",").toLowerCase(Locale.US).split(",");
if (words.length > 0)
{
return words;
}
}
return null;
}
/**
* 两个方法都注明方法只能被调用一次,由于RequestBody是流的形式读取, 那么流读了一次就没有了,所以只能被调用一次。 既然是因为流只能读一次的原因,那么只要将流的内容保存下来,就可以实现反复读取了
*
*/
public static class ReaderReuseHttpServletRequestWrapper extends HttpServletRequestWrapper
{
private final byte[] body;
public ReaderReuseHttpServletRequestWrapper(HttpServletRequest request) throws IOException
{
super(request);
body = IOUtils.toString(request.getReader()).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException
{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException
{
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream()
{
@Override
public int read() throws IOException
{
return bais.read();
}
};
}
}
public Object parseJSONString(String jsonString,HttpServletRequest request,HttpServletResponse response)
{
JSONArray jsonArray = null;
JSONObject jsonObj = null;
if (jsonString.startsWith("["))
{
jsonArray = JSONArray.parseArray(jsonString);
return parseJSONArr(jsonArray,request,response);
}
else
{
jsonObj = JSONObject.parseObject(jsonString);
return parseJSONObj(jsonObj,request,response);
}
}
public Map<String, Object> parseJSONObj(JSONObject jsonObj,HttpServletRequest request,HttpServletResponse response)
{
Map<String, Object> paramMap = new HashMap<String, Object>();
if (null == jsonObj)
{
return paramMap;
}
String val = "";
Object obj = null;
for (Map.Entry<String, Object> entry : jsonObj.entrySet())
{
val = JSONObject.toJSONString(entry.getValue());
if (val.startsWith("{") || val.startsWith("["))
{
obj = parseJSONString(val,request,response);
paramMap.put(entry.getKey(), obj);
}
else
{
if (containsIllegalWord(String.valueOf(entry.getValue())))
{
logger.info("XssFilter filtered invalid input param \"" + entry.getKey() + "\" with values \""
+ entry.getValue() + "\", and will redirect to error page.");
throw new ESysAdminException("invalid request");
}
paramMap.put(entry.getKey(), entry.getValue());
}
}
return paramMap;
}
public List<Object> parseJSONArr(JSONArray jsonArray,HttpServletRequest request,HttpServletResponse response)
{
List<Object> paramList = new ArrayList<Object>();
if (null == jsonArray)
{
return paramList;
}
String val = "";
Object obj = null;
Iterator<Object> iterator = jsonArray.iterator();
while (iterator.hasNext())
{
obj = iterator.next();
val = JSONObject.toJSONString(obj);
if (val.startsWith("{") || val.startsWith("["))
{
paramList.add(parseJSONString(val,request,response));
}
else
{
if (containsIllegalWord(String.valueOf(obj)))
{
logger.error("XssFilter filtered invalid input param with values \"" + obj
+ "\", and will redirect to error page.");
throw new ESysAdminException("invalid request");
}
paramList.add(obj);
}
}
return paramList;
}
/**
* 初始化过滤器
* @param arg0
* @throws ServletException
*/
public void init(FilterConfig arg0) throws ServletException
{
// 解析必须过滤的url
String must = arg0.getInitParameter("mustFilterUrlList");
if (StringUtils.isNotBlank(must))
{
// 转换中文逗号,并且统一转换为小写字符
must = must.toLowerCase(Locale.US);
mustFilterUrlList = must.split(",");
logger.info("init XssFilter with must filter url:" + must);
}
// 解析不需要过滤的url
String not = arg0.getInitParameter("notFilterUrlList");
if (StringUtils.isNotBlank(not))
{
not = not.toLowerCase(Locale.US);
notFilterUrlList = not.split(",");
logger.info("init XssFilter with not filter url:" + not);
}
// 解析需要过滤关键词配置
String filterWords = arg0.getInitParameter("filterWords");
if (StringUtils.isNotBlank(filterWords))
{
filterWords = filterWords.trim().toLowerCase(Locale.US);
filterWordList = filterWords.split(",");
logger.info("init XssFilter with filter words:" + filterWords);
}
// 解析忽略过滤规则(安全的)关键词配置
String ignorWords = arg0.getInitParameter("ignorWords");
if (StringUtils.isNotBlank(ignorWords))
{
ignorWords = ignorWords.trim().toLowerCase(Locale.US);
ignorWordList = ignorWords.split(",");
logger.info("init XssFilter with ignor words:" + ignorWords);
}
}
/**
* 销毁过滤器
*/
public void destroy()
{
this.mustFilterUrlList = null;
this.notFilterUrlList = null;
this.filterWordList = null;
this.ignorWordList = null;
}
}