servlet過濾器

一、什麼是Servlet過濾器

過濾器是在數據交互之間過濾數據的中間組件,獨立於任何平臺或者 Servlet 容器。

Servlet過濾器可以應用在客戶機和 servlet 之間、servlet 和 servlet 或 JSP 頁面之間,以及所包括的每個 JSP 頁面之間。
二、實現一個 Servlet 過濾器 

 Servlet過濾器 API 包含javax.servlet包中的 3 個接口,分別是 Filter 、 FilterChain 和 FilterConfig 。
 實現一個 Servlet 過濾器的確要經歷三個步驟。

  1、編寫 Servlet 過濾器實現類。
  2、配置 Servlet 過濾器。把該過濾器添加到 Web 應用程序中(通過在 Web 部署描述符 /web.xml 中聲明它);
  3、部署 Servlet 過濾器。把過濾器與應用程序一起打包並部署它;

 Servlet容器對部署描述符中聲明的每一個過濾器,只創建一個實例(或實例池)。
 與Servlet類似,容器將在同一個過濾器實例上運行多個線程來同時爲多個請求服務,因此,開發過濾器時,也要注意線程安全的問題。 

三、編寫過濾器實現類

 1、Filter接口:所有的Servlet過濾器類都必須實現javax.servlet.Filter接口
  a、init(FilterConfig):
   這是Servlet過濾器的初始化方法,Servlet容器創建Servlet過濾器實例後將調用這個方法。在這個方法中可以讀取web.xml文件中Servlet過濾器的初始化參數。

  b、doFilter(ServletRequest,ServletResponse,FilterChain):
   這個方法完成實際的過濾操作,當客戶請求訪問於過濾器關聯的URL時,Servlet容器將先調用過濾器的doFilter方法。FilterChain參數用於訪問後續過濾器。

  c、destroy():
   Servlet容器在銷燬過濾器實例前調用該方法,這個方法中可以釋放Servlet過濾器佔用的資源。

 2、過濾器實現類創建步驟(過濾器實現類生命週期):
  a.實現javax.servlet.Filter接口。
  b.初始化:實現init方法,讀取過濾器的初始化參數。
  c.過濾:實現doFilter方法,完成對請求或響應的過濾。
  d.轉發或阻塞:調用FilterChain接口對象的doFilter方法,向後續的過濾器傳遞請求或響應。
  e.析構:destroy方法銷燬過濾器,釋放過濾器佔用的資源。

四、配置 Servlet 過濾器(在web.xml中配置) 

 過濾器通過 web.xml 文件中的兩個 XML 標籤來聲明: 
  1、<filter> : 定義過濾器的名稱,並且聲明過濾器實現類和 init() 參數。 
   <filter-name> : 指定過濾器的名字;
   <filter-class> : 指定過濾器類的類名,包括類的路徑;
   <init-param> : 爲過濾器實例提供初始化參數,可以有多個;

  2、<filter-mapping> : 將過濾器與 servlet 或 URL 模式相關聯。 
   <filter-name> :  指定過濾器的名字,與<filter>中的子元素<filter-name>相對應;
   <url-pattern> :  指定和過濾器關聯的URL,爲”/*”表示所有URL;

  3、<filter-mapping>元素還可以包含0到4個<dispatcher>,指定過濾器對應的請求方式,
    可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默認REQUEST.

 REQUEST
     當用戶直接訪問頁面時,Web容器將會調用過濾器。如果目標資源是通過RequestDispatcher的include()或forward()方法訪問時,那麼該過濾器就不會被調用。
   INCLUDE
   如果目標資源是通過RequestDispatcher的include()方法訪問時,那麼該過濾器將被調用。除此之外,該過濾器不會被調用。
   FORWARD
   如果目標資源是通過RequestDispatcher的forward()方法訪問時,那麼該過濾器將被調用,除此之外,該過濾器不會被調用。
   ERROR
     如果目標資源是通過聲明式異常處理機制調用時,那麼該過濾器將被調用。除此之外,過濾器不會被調用。

 在web.xml中配置Servlet和Servlet過濾器,應該先聲明過濾器元素,再聲明Servlet元素。
 兩個或更多個過濾器應用到同一個資源,按照它們在配置文件中顯示的先後次序調用它們。

 例子1:單個過濾器配置:容器將其應用於所有接收的請求

 <filter>    
  <filter-name>FilterName</filter-name>    
  <filter-class></filter-class>  
 </filter>  
 <filter-mapping>    
  <filter-name>FilterName</filter-name>    
  <url-pattern>/*</url-pattern>  
 </filter-mapping>

  例子2:過濾器應用到特定目錄或資源(文件)的配置:此容器只有在接收到對 /mydocs 目錄中的資源的請求時纔會應用該過濾器。

   <filter>    
  <filter-name>FilterName</filter-name>    
  <filter-class>packageName.FilterName</filter-class>  
 </filter>  
 <filter-mapping>    
  <filter-name>FilterName</filter-name>    
  <url-pattern>/mydocs/*</url-pattern>  
 </filter-mapping>

 例子3:定義一個過濾器鏈:兩個或更多個過濾器應用到同一個資源,按照它們在配置文件中顯示的先後次序調用它們。

   <filter>    
  <filter-name>FilterOne</filter-name>    
  <filter-class>packageName.FilterOne</filter-class>  
 </filter>  
 <filter-mapping>    
  <filter-name>FilterOne</filter-name>    
  <url-pattern>/*</url-pattern>  
 </filter-mapping>
   <filter>    
  <filter-name>FilterTwo</filter-name>    
  <filter-class>packageName.FilterTwo</filter-class>  
 </filter>  
 <filter-mapping>    
  <filter-name>FilterTwo</filter-name>    
  <url-pattern>/mydocs/*</url-pattern>  
 </filter-mapping> 

五、部署 Servlet 過濾器

 只需把過濾器類和其他 Web 組件類包括在一起,把 web.xml 文件(連同過濾器定義和過濾器映射聲明)放進 Web 應用程序結構中,servlet 容器將處理之後的其他所有事情。

六、Servlet 過濾器實現注意事項

 1.由於Filter、FilterConfig、FilterChain都是位於javax.servlet包下,並非HTTP包所特有的,
 所以ServletRequest、ServletResponse在使用前都必須先轉換成HttpServletRequest、HttpServletResponse再進行下一步操作。

 2.在web.xml中配置Servlet和Servlet過濾器,應該先聲明過濾器元素,再聲明Servlet元素。

 3.如果要在Servlet中觀察過濾器生成的日誌,應該確保在server.xml的localhost對應的<host>元素中配置如下<logger>元素:

  <Logger className = “org.apache.catalina.logger.FileLogger”
   directory = “logs”prefix = “localhost_log.”suffix=”.txt”
   timestamp = “true”/>

七、過濾器的實現方式

 過濾器實現方式在不保證功能前提下,從性能角度考慮有如下先後順序:Decorator或Proxy模式;AOP攔截器。

 1,標準Servlet控制器
  包裝了請求和響應對象。
  web容器來充當過濾管理器(FilterManager)和過濾鏈(FilterChain)來管理和協調過濾器。
  
  問題:功能覆蓋範圍上,也是一個scope,如果功能需求要爲某個類的方法實現實現過濾,使用一個Servlet Filter這樣過濾器實現。
   但是它對所有的Servlet請求都進行過濾,這無疑範了殺雞取卵的錯誤,會造成系統性能上的損失。

 2,使用Decorator模式來定製過濾器
  如果過濾器是業務邏輯的一部分,而且在設計時,我們可以確定這些過濾器,進行特定指定的攔截。
  Decorator模式在點上針對性相當強,特別在這個點上有一系列過濾器需要實現時。  
  如果某個過濾功能是很多類都需要的,會形成很多Decorator附加類,造成點形成面的情況,則升級使用AOP攔截器。

  優點:能夠動態地爲過濾器擴展功能。
   也可以使用FilterManager和FilterChain過濾器鏈負責協調和管理過濾處理,這樣單獨的過濾器就不用和其他過濾器直接通信了。

  問題:不能以一種標準的可移植的方式支持對請求和響應對象的包裝,而且不能夠修改請求對象。
   缺乏完善的緩存機制。當過濾器要控制輸出流的時候,還必須引入某種形式的緩存機制。

 3,使用模板方法模式來定製過濾器。
  可以和其他方法混用。
  優點:這種方式是基於標準的過濾器的,基本過濾器作爲一個基類,封裝了過濾器API的所有細節。
   專注於預處理和後處理的所有邏輯。
   基本過濾器聲明瞭每個過濾器要完成的方法,每個過濾器子類來定義這些定義方法。由超類來控制子類的控制流程。 
 4,使用AOP框架攔截器。
  當功能不是針對某個具體類或方法(方法權限除外),而是一系列類,使用動態AOP攔截器,性能損耗也是值得的,而且是必要的。  
八、過濾器的應用場合

簡潔的說法:
 1.認證過濾:對用戶請求進行統一認證。 
 2.登錄和審覈過濾:對用戶的訪問請求進行審覈和對請求信息進行日誌記錄。

 3,數據過濾:對用戶發送的數據進行過濾,修改或替換。

 4.圖像轉換過濾 :轉換圖像的格式。

 5.數據壓縮過濾 :對請求內容進行解壓,對響應內容進行壓縮。

 6.加密過濾 :對請求和響應進行加密處理。

 7.令牌過濾 :身份驗證

 8.資源訪問觸發事件過濾 :

 9.XSL/T過濾

 10.Mime-type過濾

複雜的說法:
在適合使用裝飾過濾器模式或者攔截器模式的任何地方,都可以使用過濾器:

加載:對於到達系統的所有請求,過濾器收集諸如瀏覽器類型、一天中的時間、轉發 URL 等相關信息,並對它們進行日誌記錄。

性能:過濾器在內容通過線路傳來並在到達 servlet 和 JSP 頁面之前解壓縮該內容,然後再取得響應內容,並在將響應內容發送到客戶機機器之前將它轉換爲壓縮格式。

安全:過濾器處理身份驗證令牌的管理,並適當地限制安全資源的訪問,提示用戶進行身份驗證和/或將他們指引到第三方進行身份驗證。
 過濾器甚至能夠管理訪問控制列表(Access Control List,ACL),以便除了身份驗證之外還提供授權機制。
 將安全邏輯放在過濾器中,而不是放在 servlet 或者 JSP 頁面中,這樣提供了巨大的靈活性。
 在開發期間,過濾器可以關閉(在 web.xml 文件中註釋掉)。
 在生產應用中,過濾器又可以再次啓用。此外還可以添加多個過濾器,以便根據需要提高安全、加密和不可拒絕的服務的等級。

會話處理:將 servlet 和 JSP 頁面與會話處理代碼混雜在一起可能會帶來相當大的麻煩。
 使用過濾器來管理會話可以讓 Web 頁面集中精力考慮內容顯示和委託處理,而不必擔心會話管理的細節。

XSLT 轉換:不管是使用移動客戶端還是使用基於 XML 的 Web 服務,無需把邏輯嵌入應用程序就在 XML 語法之間執行轉換的能力都絕對是無價的。

九、MVC 體系結構中的Servlet過濾器 

 不管過濾器處於什麼位置,過濾器在處理流中的應用都是相同的。過濾器旨在擴充 MVC 體系結構的請求/響應處理流。
 從 MVC 的觀點看,調度器組件(它或者包括在控制器組件中,或者配合控制器組件工作)把請求轉發給適當的應用程序組件以進行處理。
 這使得控制器層成爲包括 Servlet 過濾器的最佳位置。通過把過濾器放在控制器組件本身的前面,過濾器可以應用於所有請求,
 或者通過將它放在控制器/調度器與模型和控制器之間,它可以應用於單獨的 Web 組件。 

十、應用示例或說明

 1,使用過濾器認證用戶: 
 每個過濾器也可以配置初始化參數,可以將不需要過濾的地址配置到這個Filter的配置參數中,
 過濾時,如果請求地址在配置參數中,則放行,這樣就避免了在程序中硬編碼。
 每個Filter中初始化時,都可以得到配置對象,在Filter中配置二個不需要過濾的地址,一個是登陸頁面,一個是執行登陸認證的servlet;

 2,登錄和審覈過濾的示例:使用 servlet 過濾器來控制終端用戶對應用程序特性的訪問:
 通過顯示基於用戶角色的用戶界面來控制對應用程序特性的訪問。企業用戶能夠訪問特定頁面,但個人用戶不能訪問這樣的頁面。
 應用一個過濾器來處理用戶請求並返回合適的頁面。通過使用過濾器,您能夠向一個基於 JSP 的應用程序添加這種類型的訪問控制而無需更改現有的代碼。  
 這種基於過濾器的訪問控制模型也很靈活,因爲把用戶角色映射到特定 JSP 的數據存儲在一個 XML 文件中。
 因此,您可以修改映射而不用修改應用程序 — 不需要重新編譯或重新部署。

實施:
     每當用戶從一個包含 /controllerservlet模式的 URI 請求資源時就會調用AccessControlFilter的實例
 當訪問控制過濾器被初始化後,從映射文件讀取數據,而 AccessControlFilter.doFilter 方法負責處理過濾事務。
 在獲取了用戶角色和所請求頁面的 URL 之後,doFilter 把這些值與映射數據對比。
 如果所請求的頁面對用戶角色是合適的,那麼代碼將調用 chain.doFilter 方法來調用該頁面並繼續正常的處理。
 否則,代碼將在 chain.doFilter 之前調用 request.setAttribute,結果是 Controller Servlet 使用戶重新進入到登錄頁面。
 
相關文件:
1、web.xml —— Web 部署描述符文件

 <filter>    
  <filter-name>AccessControlFilter</filter-name>    
  <filter-class>oracle.otnsamples.ibfbs.control.AccessControlFilter</filter-class>      
 </filter>    
 <filter-mapping>     
  <filter-name>AccessControlFilter</filter-name>     
  <url-pattern>/controllerservlet</url-pattern>  
 </filter-mapping>    
2、Control.xml —— 描述事件、用戶角色和 JSP 聯繫的xml文件

  <Event>
     <Name>BUYSTOCK</Name>
     <Class>oracle.otnsamples.ibfbs.trademanagement.helper.TradeManagementHelper</Class>
     <Method>buyStock</Method>
     <Screen>jsps/BuyStock.jsp</Screen>

     <Roles>
        <Role>USER</Role>
     </Roles> 
  </Event> 
   ...
  <Event>
     <Name>CORPUPLOAD</Name>
     <Class></Class>
     <Method></Method>
     <Screen>jsps/CorporateUpload.jsp</Screen>
     
  <Roles>
       <Role>CORP</Role>
     </Roles> 
  </Event> 
   ...
  <Event>
     <Name>CONFIGNEWSUPLOAD</Name>
     <Class>oracle.otnsamples.ibfbs.admin.helper.AdminHelper</Class>
     <Method>configNewsUpload</Method>

     <Screen>jsps/UploadData.jsp</Screen>
     <Roles>
        <Role>ADMIN</Role>
     </Roles> 
  </Event>
   ...
  <Event>
     <Name>LOGIN</Name>
     <Class>oracle.otnsamples.ibfbs.usermanagement.helper.UserManagementHelper</Class>
     <Method>checkPassword</Method>
     <Screen>jsps/MyHome.jsp</Screen>

     <Roles>
        <Role>DEFAULT</Role>
        <Role>USER</Role>
        <Role>CORP</Role>
        <Role>ADMIN</Role>
     </Roles> 
  </Event>    

3、AccessControlFilter.java —— 過濾器類 

 //讀取Control.xml,對用戶的權限做檢查

 

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)                       
  throws IOException, ServletException { 
   
  HttpSession session = ((HttpServletRequest) request).getSession();    
  String eventName = request.getParameter("EVENTNAME");    
  if (eventName != null && urlMap != null ) {      
   String role = (String) session.getAttribute("ROLE");      
   if (role == null) {
    role = "DEFAULT";      
   }

   URLMapping event = (URLMapping) urlMap.get(eventName);      
   
   if ((event != null) && (event.getRoles() != null) && (event.getRoles().length > 0)) {        
    // New session so not logged in yet. Redirect to login page        
    if (session.isNew())  {        
     request.setAttribute("EVENTNAME", "FIRSTPAGE"); 
    }       
    // If invalid access, redirect to login page        
    else  if (!event.isValidRole(role))  {        
     request.setAttribute("EVENTNAME", "LOGINPAGE"); 
    }     
   }
    
  }else {      
   request.setAttribute("EVENTNAME", "FIRSTPAGE");    
  }    
  // The privileges are sufficient to invoke this URL, continue normal    
  // processing of the request    
  chain.doFilter(request, response);  
 } 

 4、對請求信息進行日誌記錄的示例 

public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws ServletException, IOException {
       //把ServletRequest對象構造爲HttpServletRequest

   //從請求中提出需要的進行日誌記錄的信息
       String url = req.getRequestURI();

       HttpSession so = req.getSession();
       String canLog = (String)so.getAttribute(url);

       //如果第一次訪問該頁面,就進行日誌處理
       if (canLog == null) {
        so.setAttribute(url, "Y");
        doLog();
       }
       chain.doFilter(request,response);
  } 

5、用過濾器來解決客戶端和服務器端編碼一致,防止中文亂碼的問題。  

public class CharacterEncodingFilter implements Filter {
    protected FilterConfig filterConfig = null;
    protected String encoding = "";

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
     throws IOException, ServletException {
           if(encoding != null) {
             servletRequest.setCharacterEncoding(encoding);
    }
      filterChain.doFilter(servletRequest, servletResponse);
    }
    public void destroy() {
      filterConfig = null;
      encoding = null;
   }
       public void init(FilterConfig filterConfig) throws ServletException {
      this.filterConfig = filterConfig;
           this.encoding = filterConfig.getInitParameter("encoding");

    }
  }

 6、使 Browser瀏覽器 不緩存頁面的過濾器

  public class ForceNoCacheFilter implements Filter {

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 
    throws IOException, ServletException {
 
    ((HttpServletResponse) response).setHeader("Cache-Control","no-cache");
      ((HttpServletResponse) response).setHeader("Pragma","no-cache");
      ((HttpServletResponse) response).setDateHeader ("Expires", -1);
      filterChain.doFilter(request, response);
    }
  }

7、用於檢測用戶是否登陸的過濾器,如果未登錄,則重定向到指的登錄頁面

  /**
   * 用於檢測用戶是否登陸的過濾器,如果未登錄,則重定向到指的登錄頁面<p>
   * 配置參數<p>
   * checkSessionKey 需檢查的在 Session 中保存的關鍵字<br/>
   * redirectURL 如果用戶未登錄,則重定向到指定的頁面,URL不包括 ContextPath<br/>
   * notCheckURLList 不做檢查的URL列表,以分號分開,並且 URL 中不包括 ContextPath<br/>
   */
  

public class CheckLoginFilter implements Filter {
    protected FilterConfig filterConfig = null;
       private String redirectURL = null;
    private List notCheckURLList = new ArrayList();
    private String sessionKey = null;

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
    throws IOException, ServletException {

      HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

           HttpSession session = request.getSession();
      
    if(sessionKey == null) {
        filterChain.doFilter(request, response);
        return;
      }
      if((!checkRequestURIIntNotFilterList(request)) && 
      session.getAttribute(sessionKey) == null) {
        response.sendRedirect(request.getContextPath() + redirectURL);
        return;
      }
      filterChain.doFilter(servletRequest, servletResponse);
    }

    public void destroy() {
      notCheckURLList.clear();
    }

    private boolean checkRequestURIIntNotFilterList(HttpServletRequest request) {

      String uri = request.getServletPath() + (request.getPathInfo() == null ? "" : request.getPathInfo());
      return notCheckURLList.contains(uri);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
      this.filterConfig = filterConfig;
      redirectURL = filterConfig.getInitParameter("redirectURL");
           sessionKey = filterConfig.getInitParameter("checkSessionKey");

      String notCheckURLListStr = filterConfig.getInitParameter("notCheckURLList");

      if(notCheckURLListStr != null) {
        StringTokenizer st = new StringTokenizer(notCheckURLListStr, ";");
        notCheckURLList.clear();
        while(st.hasMoreTokens()) {
          notCheckURLList.add(st.nextToken());
        }
      }
    }
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章