Spring boot中使用servlet filter
liuyuhang原創,未經允許請勿轉載!
在web項目中經常需要一些場景,如參數過濾防止sql注入,防止頁面攻擊,空參數矯正等,
也可以做成token驗證,session驗證,點擊率統計等。
為了這種業務,經常會需要寫過濾器(Filter)。
servlet提供的默認過濾器比較好用,配置也還算方便;
轉入springboot開發后,注解也並不復雜,原理依舊。
使用filter的步驟並不復雜,主要分為幾個步驟:
1、新建class 實現Filter抽象類(javax.servlet.Filter)
2、override三個方法(init,doFilter,destroy)
3、編寫ParameterRequestWrapper類繼承HttpServletRequestWrapper類(稍后說明原因)
4、ParameterRequestWrapper類構造器
5、構造器中復寫父類構造器並將request.getParameterMap加入子類成員變量
6、編寫addParam方法留用
7、修改參數並調用ParameterRequestWrapper實例保存params
8、調用doFilter方法中的FilterChain變量,將修改后的request重新封裝
9、在springboot入口方法中添加注解@ServletComponentScan注冊filter
其中1、2步驟為必須的,其余的步驟為修改request參數使用的封裝方式。
1、新建class實現Filter抽象類(javax.servlet.Filter);
2、override三個方法(init,doFilter,destroy);代碼如下:
1 package lyh.java.filterAndInteceptor; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.annotation.WebFilter; 12 13 import org.springframework.core.annotation.Order; 14 15 @Order(1)//多個filter的時候,該序號越小,越早執行 16 @WebFilter(filterName = "FirstFilter", urlPatterns = "/*")//url過濾配置,並非包配置 17 public class MyMvcFilter implements Filter{ 18 19 @Override 20 public void init(FilterConfig filterConfig) throws ServletException { 21 //這里寫init邏輯,該init將在服務器啟動時調用 22 } 23 24 @Override 25 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 26 //request處理邏輯 27 //request在封裝邏輯 28 //chain重新寫回request和response 29 } 30 31 @Override 32 public void destroy() { 33 //這里寫destroy邏輯,該destroy邏輯將在服務器關閉時調用 34 } 35 }
9、在springboot入口方法中添加注解@ServletComponentScan注冊filter,代碼如下:
1 package lyh.java; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.SpringBootConfiguration; 5 import org.springframework.boot.web.servlet.ServletComponentScan; 6 7 import lyh.java.config.DataSourceConfig; 8 9 @SpringBootConfiguration//springboot基礎配置注解 10 @ServletComponentScan//springboot servlet filter 11 public class Run { 12 13 public static void main(String[] args) throws Exception { 14 SpringApplication.run(Run.class, args); 15 } 16 }
到這里,就完成了Filter的簡單創建了,可以自行測試使用
創建Filter,一般目的是為了過濾一些參數,如果是為了中斷該訪問,應該是創建Interceptor
對於java中的mvc,過濾或者修改參數,針對的主要是request中的parameterMap的修改。
當然還有很多好用的操作,這里不做討論了。
修改request實例,思路上有兩個步驟是必須要考慮的:
①如何獲取request中的parameterMap並且修改?
②如何將修改后的parameterMap重新傳遞回去,使得controller能夠不受影響?
對於如何解決以上兩個問題,思路上也要經過兩個步驟:
①常用的是request.getParameter或request.getParamerterMap方法,但是ServletRequest是一個接口;
該接口定義了getParameterMap抽象方法,只要找到該接口的實現類,繼承該類,利用java繼承原理,
相互繼承的類,調用父類方法的時候,若子類復寫了該方法,則默認調用子類方法。
以此來實現request的重新封裝。
②在子類中定義add方法,來添加新的param,同時該param與request中原有的param進行校對,去重,
保證子類的覆蓋父類的,這樣獲取param的時候,不會有所察覺。
在一番查找之后,發現了一個類,來專門處理request對象的,截圖如下
注意類注解:Methods default to calling through to the wrapped request object.意為request默認調用該封裝對象
注意截圖中的構造器,該構造器傳入servletRequest對象,因此該帶參構造器肯定要被子類重寫
於是,編寫一個類,繼承該類,同時修改構造器,復寫getParameter方法,提供一個map作為request內容的緩存;
使得復寫的getParameter能夠從此緩存中獲取param即可,代碼如下,注釋完善,不再贅述:
1 package lyh.java.tools; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletRequestWrapper; 8 9 /** 10 * 重寫ParameterRequestWrapper繼承HttpServletRequestWrapper並重寫部分方法 11 * 構造器調用父類構造器 12 * 重寫param作為緩存 13 * 重寫getParameter獲取子類緩存 14 * 增加兩個add方法修改該緩存 15 * @author Liuyuhang 16 * 17 */ 18 public class ParameterRequestWrapper extends HttpServletRequestWrapper { 19 20 /** 21 * 初始化params,作為getParameter方法獲取的參數列表,內首先包含req中的paramMap,再修改 22 */ 23 private Map<String , String[]> params = new HashMap<String, String[]>(); 24 25 /** 26 * 重載構造器,並調用父類構造器,將參數寫入子類params 27 * @param request 28 */ 29 public ParameterRequestWrapper(HttpServletRequest request) { 30 super(request);// 將request交給父類,以便於調用對應方法的時候,將其輸出,其實父親類的實現方式和第一種new的方式類似 31 this.params.putAll(request.getParameterMap());//將參數表,賦予給當前的Map以便於持有request中的參數 32 } 33 34 /** 35 * 重寫getParameter,代表參數從當前類中的map獲取 36 */ 37 @Override 38 public String getParameter(String name) { 39 String[]values = params.get(name); 40 if(values == null || values.length == 0) { 41 return null; 42 } 43 return values[0]; 44 } 45 46 /** 47 * 獲取子類param緩存的方法 48 */ 49 public Map<String,String[]> getParamsMap(){ 50 return this.params; 51 } 52 /** 53 * 寫入一個map的方法 54 * @param map 55 */ 56 public void addParamsMap(Map<String , Object> map) { 57 for(Map.Entry<String , Object>entry : map.entrySet()) { 58 addParam(entry.getKey() , entry.getValue()); 59 } 60 } 61 /** 62 * 寫入一個參數的方法 63 * @param name 64 * @param obj 65 */ 66 public void addParam(String name , Object obj) { 67 if(obj != null) { 68 if(obj instanceof String[]) { 69 params.put(name , (String[])obj); 70 }else if(obj instanceof String) { 71 params.put(name , new String[] {(String)obj}); 72 }else { 73 params.put(name , new String[] {String.valueOf(obj)}); 74 } 75 } 76 } 77 78 }
返回Filter類中的doFilter方法,對該方法進行加工。思路是:
①封裝ServletRequest實例為子類抽象HttpServletRequest對象實例,調用剛剛寫的方法的構造器
將ParameterRequestWrapper實例化得實例對象rpw;
②獲取該prw的param,一一過濾,如字符串trim,修改null或者空字符串,修改時間日期格式,
獲取token來進行身份驗證之類。
③調用doFilter中的FilterChain參數,將重新寫過的request和response寫回。
具體代碼如下:
1 @Override 2 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { 3 //將request封裝為子類 4 HttpServletRequest httpReq = (HttpServletRequest) req; 5 //調用復寫的構造器重新封裝request 6 ParameterRequestWrapper prw = new ParameterRequestWrapper(httpReq); 7 //這里寫requtst過濾內容的邏輯,如字符串trim,修改null或者空字符串,修改時間日期格式,獲取token來進行身份驗證之類。 8 prw.addParam("user", "Filter-user"); 9 //調用chain執行filter並寫回參數 10 chain.doFilter(prw, resp); 11 }
寫個類測試下。代碼如下:
1 package lyh.java.controller; 2 3 import javax.servlet.http.HttpServletRequest; 4 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RestController; 7 8 @RestController//restFull Controller注解,返回json格式 9 public class hello { 10 11 @RequestMapping("/hello") 12 public String helloTest(HttpServletRequest req) throws Exception { 13 String user = req.getParameter("user"); 14 System.out.println("user:"+user); 15 return "hello world! "+user; 16 } 17 18 19 }
運行結果如下:
完工!
以上!✧(∗≧ꇴ≦)人(≧ꈊ≦∗)✧