springboot/tomcat使用filter实现防御xss攻击、sql注入、目录遍历等
作为一个有经验的Java web开发人员,相信大家都知道拦截器intercept和过滤器filter,他两基本可以实现的功能都差不多,下面简单说一下其区别:
1.filter是servlet的内容,对servlet的扩展都是基于filter完成
2.intercept是spring mvc框架的内容,只能在spring mvc项目中使用
3.spring mvc实现了servlet规范,所有前端请求执行顺序,请求intercept拦截器链》请求filter过滤器链》具体服务》响应filter过滤器链》响应intercept拦截器链
两个注解
@WebFilter(filterName = "securityFilter", urlPatterns = "/*")
@Component
@Component是spring的注解,springboot会扫描此类并添加到sevlet的FilterChain中
@WebFilter主要是提供的容器扫描加载,如tomcat容器,将filter打包放在lib目录下即可,省去web.xml中配置filter和filter-mapping,如下
<filter> <filter-name>ruphyFilter</filter-name> <filter-class> me.muphy.tomcat.filter.RequestFilter </filter-class> </filter> <filter-mapping> <filter-name>ruphyFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
xss或sql注入防御,就是对前端的不信任,需要对前端的输入做安全校验、过滤和替换,我见过有人写了很多的代码去列举并替换危险的字符串,限制了大量用户输入,却只是看起来安全,治标不治本
要修改过滤请求中的参数,显然不能直接修改原来的ServletRequest对象,首先想到的肯定是(代理)包装器模式,但如何包装呢,虽然包装器需要大量修改的地方只有前端相关的方法,其他大部分都是直接调被包装的方法,但要实现HttpServletRequest接口的方法太多,通过分析源码发现只需要继承HttpServletRequestWrapper这个类即可只复写我们关注的方法,然后调用filterChain.doFilter方法时传入包装类即可
下面我实现了一个很简单但功能强大的安全filter,共参考,代码如下:
package me.muphy.filter; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.IOException; import java.util.HashMap; import java.util.Map; @WebFilter(filterName = "securityFilter", urlPatterns = "/*") @Component public class SecurityFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if (servletRequest instanceof HttpServletRequest) { HttpServletRequest request = (HttpServletRequest) servletRequest; filterChain.doFilter(new SecurityHttpServletRequestWrapper(request), servletResponse); } else { filterChain.doFilter(servletRequest, servletResponse); } } /** * 此几行正则表达式 史上最强防御xss攻击、sql注入、目录遍历等,却较少影响用户使用特殊字符和关键字 */ public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper { private Map<String, String> regRep = new HashMap<>(); public SecurityHttpServletRequestWrapper(HttpServletRequest request) { super(request); regRep.put("'([^']*)'", "‘$1’"); regRep.put("`([^`]*)`", "~$1~");
regRep.put("'([^ ]* )", " $1"); regRep.put("(\\.\\.[\\\\/]+)+", ""); regRep.put("'(.*--+)", "‘$1"); regRep.put("\\\"([^\\\"]*)\\\"", "“$1”"); regRep.put("\\(([^\\)]*)\\)", "($1)"); regRep.put("<([^>]+)>", "<$1>"); } // 上次直接替换还是会被绕过 这里更新使用while循环替换 private String replace(String val) { if (val != null) { for (String key : regRep.keySet()) { String nval = val; val = val.replaceAll(key, regRep.get(key)); while (!val.equals(nval)) { nval = val; val = val.replaceAll(key, regRep.get(key)); } } } return val; } @Override public String getHeader(String name) { return replace(super.getHeader(name)); } @Override public Cookie[] getCookies() { Cookie[] cookies = super.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { cookies[i].setValue(replace(cookies[i].getValue())); } } return cookies; } @Override public String getQueryString() { return replace(super.getQueryString()); } @Override public String getParameter(String name) { return replace(super.getParameter(name)); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values != null) { for (int i = 0; i < values.length; i++) { values[i] = replace(values[i]); } } return values; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> parameterMap = super.getParameterMap(); if (parameterMap != null) { for (String key : parameterMap.keySet()) { String[] values = parameterMap.get(key); if (values != null) { for (int i = 0; i < values.length; i++) { values[i] = replace(values[i]); } } } } return parameterMap; } } }