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; } } }