大資料WEB階段(十五)JavaEE三大核心技術之過濾器
阿新 • • 發佈:2019-01-03
Filter過濾器
一、Filter 過濾器概述
- Filter是JavaEE三大核心技術(Servlet 、 Filter 、 Listener)之一
- FIlter作用是攔截對資源的訪問 , 攔截下來後可以控制是否允許通過 , 或者在允許通過前後做一些額外的操作 。
- 所謂的攔截其實就是對代表請求的request、物件和代表 響應的response物件攔截下來 , 進行控制
- 一個過濾器可能攔截多個資源 , 一個資源也可能被多個過濾器攔截
- 這種多個攔截器攔截一個資源的模式成為責任鏈模式 。
- 常用場景:
- 就與URL的訪問許可權控制
- 全站亂碼解決過濾器
- 過濾敏感詞彙
- 壓縮響應
二、 過濾器的開發
- 想要開發一個過濾器 , 需要兩個步驟
- 寫一個類實現Filter介面
- 在web.xml中配置過濾器
- Filter介面:
- init(FilterConfig)
- 初始化的方法 , 當Filter被初始化時 , 呼叫此方法 , 執行初始化操作
- destory()
- 銷燬方法, 在Filter被銷燬之前呼叫 , 執行善後操作
- doFilter(ServletRequest request , ServletResponse response , FilterChain chain)
- 核心方法 ,在存活期間 , 過濾器攔截到對資源的訪問 會造成此方法的執行 , 需要在這個方法中設計過濾器的核心邏輯程式碼
- init(FilterConfig)
配置過濾器
<filter> -- 配置一個過濾器 <filter-name>FirstFilter</filter-name> -- 過濾器的名字 <filter-class>com.tarena.filter.FirstFilter</filter-class> -- 過濾器的類 </filter> <filter-mapping> -- 過濾器的攔截路徑配置,可以配置多個 <filter-name>FirstFilter</filter-name> -- 為哪個名字的過濾器配置 <url-pattern>/*</url-pattern> -- 攔截哪個路徑資源可以配置多個 <servlet-name>XxxServlet</servlet-name> -- 攔截哪個名字的Servlet <dispatcher></dispatcher> -- 指定過濾器攔截哪種方式對資源的訪問,可以取值為REQUEST FORWARD INCLUDE ERROR,如果不配置,預設只攔截REQUEST方式的訪問。可以配置多個。 </filter-mapping>
三、生命週期
- 在web應用啟動時 , 會建立處web應用中配置的過濾器物件 , 創建出過濾器物件會立即呼叫init方法進行初始化操作 , 之後一直存活 , 直到web應用被銷燬時 , Filter跟著被銷燬.在銷燬之前會自動呼叫destory方法執行善後操作 。 在存活期間 , 每當攔截到資源訪問 , 就執行doFilter方法 , 來執行過濾器的 邏輯 , 如果不做操作 , 則 預設攔截 , 可以通過FilterChain類的物件的 doFilter方法實現對資源訪問的放行 。 並且可以在doFilter前後做一些操作 。
四、 細節
- 如果一個資源被多個過濾器攔截 , 多個 攔截器的攔截順序取決於在web.xml檔案中配置過濾器時的先後順序 。
- 多個 過濾器的執行 , 類似於方法 一層一層呼叫的過程 ,, 一層一層往裡鑽, 然後在一層層一層往外出 。
五、 和Filter開發相關的物件
FilterConfig:
- init方法的引數
- 代表FIlter在web.xml檔案中的配置物件
- 可以用來獲取Filter在Web.xml檔案中的初始化配置引數
可以用來獲取ServletContext物件
public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init...."); //1.filterConfig功能1:獲取filter的初始化引數 Enumeration<String> names = filterConfig.getInitParameterNames(); while(names.hasMoreElements()){ String name = names.nextElement(); String value = filterConfig.getInitParameter(name); System.out.println(name+"~"+value); } //2.獲取ServletContext物件 ServletContext sc = filterConfig.getServletContext(); }
- FilterChain:
- doFilter方法引數
- 代表過濾器鏈
- 提供了doFilter方法 ,放行當前過濾器 , 執行後續過濾器 , 如果後續沒有過濾器則呼叫到相應的資源 。
六、Filter案例
全站亂碼解決過濾器
- 在web開發過程中 , 存在請求引數亂碼和響應輸出亂碼 。
- 之前的開發中 , 在所有的Servlet和jsp頁面中 , 需要手動解決這兩種亂碼
- 可以通過開發過濾器攔截所有的資源訪問 , 在過濾器中解決全站亂碼問題 。
具體解決請求響應亂碼問題:
1. 在web.xml檔案 中配置全域性的編碼型別 <!-- 全域性配置 --> <context-param> <param-name>encode</param-name> <param-value>utf-8</param-value> </context-param> 2. 在過濾器初始化時獲取全域性配置的編碼 , 並儲存到過濾器中 private String encode = null; public void init(FilterConfig config) throws ServletException { encode = (String) config.getServletContext().getAttribute("encode"); } 3. 解決響應亂碼: 在doFilter方法中 response.setCharacterEncoding(encode); response.setContentType("text/html;charset="+encode); 4. 解決請求引數亂碼 //方案一: // request.setCharacterEncoding(encode); //只能解決Post請求引數的亂碼 //可以解決Post和Get請求型別的引數亂碼 //但是在轉碼的時候需要指定具體的引數名稱 ,轉碼之後要重新放入request中供servlet拿取更是不能實現 , 所以 不可行 // String param = new String(request.getParameter("xxxx").getBytes("iso-8859-1") , encode); //方案二: //request中的請求引數本身無法改變 //那麼 換一個思路 想辦法改造和獲取請求引數相關的方法 在方法內加上解決亂碼的程式碼 //這樣通過這些方法獲取請求引數時 解決好亂碼再返回 用起來就感覺 亂碼被解決了一樣 //改造原有request方法方案一: //繼承 //繼承只能先改造在建立例項 , 但是現在已經有了request物件, 就算通過繼承改造了ServletRequest類也不會影響到已有的物件 , 排除 //改造原有request方法方案二: //裝飾設計模式 //1. 新建一個類 , 實現與被改造物件相同的介面 //2. 通過狗仔方法傳入被改造的物件並儲存在本類中 //3. 然後實現介面中所有的方法 , 如果需要改造則在對應的方法裡寫出邏輯 , 如果不需要改造的方法 ,則直接通過傳入的沒被改造的引數物件呼叫原有的方法即可 //這種方案 的缺點 ,如果被改造的方法 中方法過多時 ,這個操作會十分繁瑣 。 //改造原有的request物件方案三: //在裝飾設計模式的基礎上實現 //通過原始碼 發現Servlet包下已經提供了一個ServletRequestWrapper類 , 它實現了與ServletRequest相同的介面 //也就是說他就是java中已經提供的供開發者修改request物件中方法的入口 //新建一個類繼承ServletRequestWrapper類之後 ,通過構造方法 把原始物件傳進去 , 然後只重寫需要改造的方法即可 程式碼// public class EncodeFilter implements Filter{ /** * 當前web應用編碼集 */ private String encode = null; /** * 初始化方法 */ public void init(FilterConfig filterConfig) throws ServletException { //獲取ServletContext物件 ServletContext sc = filterConfig.getServletContext(); //讀取初始化引數 中的 編碼集 配置 this.encode = sc.getInitParameter("encode"); } /** * 過濾方法 */ public void doFilter(final ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { //--全站響應亂碼 response.setCharacterEncoding(encode); response.setContentType("text/html;charset="+encode); //--全站請求亂碼 - 通過裝飾器 裝飾request 修改其中的和獲取請求引數相關的方法 增加了亂碼解決的程式碼 ServletRequest myReq = new MyServletRequest((HttpServletRequest) request); //放行資源 chain.doFilter(myReq, response); } /** * 銷燬方法 */ public void destroy() { } /** * 內部類 ServletRequest的裝飾類 改造了獲取請求引數相關的方法 增加了亂碼解決的程式碼 */ //繼承了HttpServletRequestWrapper ,這個父類本身就是 HttpServletRequest的裝飾器 在其中提供方法的預設的實現 不想改造的方法 不用管 想改造的方法 覆蓋父類方法即可 class MyServletRequest extends HttpServletRequestWrapper{ private ServletRequest request = null; private boolean hasNotEncode = true; //構造器 接受傳入的request儲存在類的內部 public MyServletRequest(HttpServletRequest request) { super(request); this.request = request; } //覆蓋和獲取請求引數相關的方法 @Override public Map<String,String[]> getParameterMap() { try { //1.獲取真正request的請求引數組成的map Map<String,String[]> map = request.getParameterMap(); if(hasNotEncode){//由於真正的request對此map會快取 所以解決亂碼的操作 只需要做一次 此處通過hasNotEncode來控制 //2.遍歷map for(Map.Entry<String, String[]>entry : map.entrySet()){ //3.獲取當前遍歷到的值的陣列 String [] values = entry.getValue(); //4.遍歷值的陣列 for(int i = 0;i<values.length;i++){ //5.解決亂碼 存回陣列 values[i] = new String(values[i].getBytes("iso8859-1"),encode); } } hasNotEncode = false; } //6.返回解決完亂碼的map return map; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } }
使用者30天內自動登入
- 在處理使用者登入時 , 判斷使用者是否勾選30天內自動登入 , 如果使用者名稱密碼正確且勾選過該選項 , 則傳送cookie , 將使用者名稱密碼儲存30天。 為了安全起見 , 儲存之前先對密碼進行MD5加密
- 之後使用者在來訪問時經過自動登入過濾器被攔截 , 如果使用者未登入 , 且帶了自動登入的cookie , 並且其中的使用者名稱密碼都正確 , 則給給使用者給自動登入 。但是無論自動登入與否都要對url放行 。
- 在LoginServlet中新增使用者自動登入邏輯: 如果使用者勾選 了自動登入 , 則將使用者資訊新增進cookie中儲存在本地
- 在AutoLoginFilter中 攔截所有請求 , 先判斷是否登入 , 在判斷是否有自動登入cookie ,最後判斷使用者密碼是否正確
自動登入過濾器原始碼
public class AutoLoginFilter implements Filter{ public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; // 1. 判斷該使用者是否 已經登入 if(req.getSession(false) == null ||req.getSession(false).getAttribute("user") == null){ System.out.println("使用者未登入"); // 2. 判斷訪問時是否帶有自動登入cookie Cookie[] cookies = req.getCookies(); for(Cookie c :cookies){ if("autologin".equals(c.getName())){ System.out.println("帶有自動登入cookie"); String v = c.getValue(); String[] vs = URLDecoder.decode(v , "utf-8").split("#"); UserService us = BaseFactory.getBase().getInstance(UserService.class); // 3. 驗證使用者名稱密碼是否正確 User user = us.login(vs[0], vs[1]); if(user != null){ System.out.println("開始登入"); //三個條件都滿足 , 新增登入標記 req.getSession().setAttribute("user", user); System.out.println("自動登入成功"); } break; } } } // 4. 無論是否自動 登入成功 , 都放行訪問。 放行訪問 chain.doFilter(request , response); } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
七、MD5加密演算法
- 又稱資料摘要演算法 , 資料指紋演算法
- 任意長度的二進位制檔案計算出128位二進位制的再要資訊 , 通常轉換為32位16進位制顯示 。
- 明文相同算出的密文一定相同
- 明文不同算出的密文一定不同 (概率極低 , 所以一般認為是唯一的)
- 只能由明文算成密文 ,, 不能有密文算成明文
- 應用:
- 加密儲存資料
- 檔案完整性校驗
- 數字簽名