前几天有个客户在系统上写了一段html语句,打开页面就显示一张炒鸡大的图片,影响美观。后来仔细想想,幸亏注入的仅仅是html语句,知道严重性后,马上开始一番系统安全配置。
一. 定义过滤器
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 防止XSS攻击的过滤器
*/
public class XssFilter implements Filter{
/**
* 排除链接
*/
public List<String> excludes = new ArrayList<>();
/**
* 初始化
* 与我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。
* Web应用程序启动时,Web服务器将创建Filter的实例对象,并调用其init方法,读取web.xml配置,
* 完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作。
* Filter对象只会创建一次,init方法也只会执行一次。
* 开发人员通过init方法的参数,可获得代表当前Filter配置信息的FilterConfig对象。
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes)) {
String[] url = tempExcludes.split(",");
excludes.addAll(Arrays.asList(url));
}
}
/**
* 拦截请求
* 这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。
* FilterChain参数用于访问后续过滤器。
*/
@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);
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
String url = request.getServletPath();
String method = request.getMethod();
/*// GET DELETE 不过滤
if (method == null || method.matches("GET") || method.matches("DELETE")) {
return true;
}*/
if (method == null) {
return true;
}
return StringUtils.matches(url, excludes);
}
/**
* 销毁
* Filter对象创建后会驻留在内存,当Web应用移除或服务器停止时才销毁。在Web容器卸载Filter对象之前被调用。
* 该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
*/
@Override
public void destroy() {
}
}
二. 过滤处理,实现参数值过滤
import cn.hutool.http.HTMLFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* XSS过滤处理
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
// 防xss攻击和过滤前后空格
escapseValues[i] = clean(values[i]).trim();
}
return escapseValues;
}
return super.getParameterValues(name);
}
/**
* 清除所有HTML标签,但是不删除标签内的内容
*
* @param content 文本
* @return 清除标签后的文本
*/
private static String clean(String content) {
return new HTMLFilter().filter(content);
}
}
三. 使用到的字符串工具类
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 字符串工具类
*/
public class StringUtils {
/** 空字符串 */
private static final String NULLSTR = "";
/**
* 获取参数不为空值
*
* @param value defaultValue 要判断的value
* @return value 返回值
*/
public static <T> T nvl(T value, T defaultValue)
{
return value != null ? value : defaultValue;
}
/**
* * 判断一个Collection是否为空, 包含List,Set,Queue
*
* @param coll 要判断的Collection
* @return true:为空 false:非空
*/
public static boolean isEmpty(Collection<?> coll)
{
return isNull(coll) || coll.isEmpty();
}
/**
* * 判断一个Collection是否非空,包含List,Set,Queue
*
* @param coll 要判断的Collection
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Collection<?> coll)
{
return !isEmpty(coll);
}
/**
* * 判断一个对象数组是否为空
*
* @param objects 要判断的对象数组
** @return true:为空 false:非空
*/
public static boolean isEmpty(Object[] objects)
{
return isNull(objects) || (objects.length == 0);
}
/**
* * 判断一个对象数组是否非空
*
* @param objects 要判断的对象数组
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Object[] objects)
{
return !isEmpty(objects);
}
/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true:为空 false:非空
*/
public static boolean isEmpty(Map<?, ?> map)
{
return isNull(map) || map.isEmpty();
}
/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true:非空 false:空
*/
public static boolean isNotEmpty(Map<?, ?> map)
{
return !isEmpty(map);
}
/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str)
{
return isNull(str) || NULLSTR.equals(str.trim());
}
/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true:非空串 false:空串
*/
public static boolean isNotEmpty(String str)
{
return !isEmpty(str);
}
/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true:为空 false:非空
*/
public static boolean isNull(Object object)
{
return object == null;
}
/**
* * 判断一个对象是否非空
*
* @param object Object
* @return true:非空 false:空
*/
public static boolean isNotNull(Object object)
{
return !isNull(object);
}
/**
* * 判断一个对象是否是数组类型(Java基本型别的数组)
*
* @param object 对象
* @return true:是数组 false:不是数组
*/
public static boolean isArray(Object object)
{
return isNotNull(object) && object.getClass().isArray();
}
/**
* 去空格
*/
public static String trim(String str)
{
return (str == null ? "" : str.trim());
}
/**
* 删除最后一个字符串
*
* @param str 输入字符串
* @param spit 以什么类型结尾的
* @return 截取后的字符串
*/
public static String lastStringDel(String str, String spit)
{
if (!StringUtils.isEmpty(str) && str.endsWith(spit))
{
return str.subSequence(0, str.length() - 1).toString();
}
return str;
}
/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, List<String> strs)
{
if (isEmpty(str) || isEmpty(strs))
{
return false;
}
for (String pattern : strs)
{
if (isMatch(pattern, str))
{
return true;
}
}
return false;
}
/**
* 判断url是否与规则配置:
* ? 表示单个字符;
* * 表示一层路径内的任意字符串,不可跨层级;
* ** 表示任意层路径;
*
* @param pattern 匹配规则
* @param url 需要匹配的url
* @return
*/
public static boolean isMatch(String pattern, String url)
{
AntPathMatcher matcher = new AntPathMatcher();
return matcher.match(pattern, url);
}
}
四. 配置web.xml,添加过滤器
<!-- 配置过滤器防止XSS注入 -->
<filter>
<filter-name>XssFilter</filter-name>
<filter-class>com.cn.unit.filter.XssFilter</filter-class>
<init-param>
<param-name>excludes</param-name>
<!-- 静态资源不进行过滤,如js、css文件 -->
<param-value>/css/*,/js/*,/assets/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>XssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置各节点简单介绍:
节点名 | 介绍 |
---|---|
<filter> | 指定一个过滤器 |
<filter-name> | 用于为过滤器指定一个名字,该元素的内容不能为空 |
<filter-class> | 指定过滤器的完整的限定类名 |
<init-param> | 为过滤器指定初始化参数。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数 |
<param-name> | <init-param>的子元素,指定参数的名字 |
<param-value> | <init-param>的子元素,指定参数的值 |
<filter-mapping> | 设置一个Filter所负责拦截的资源。可通过Servlet名称或资源访问的请求路径指定 |
<filter-name> | 子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字 |
<url-pattern> | 设置 filter 所拦截的请求路径(过滤器关联的URL样式) |
<servlet-name> | 指定过滤器所拦截的Servlet名称 |
<dispatcher> | 指定过滤器所拦截的资源被 Servlet 容器调用的方式,默认REQUEST |