Java進階學習第二十一天——Filter學習

文檔版本 開發工具 測試平臺 工程名字 日期 作者 備註
V1.0 2016.06.03 lutianfei none

javaWeb之過濾器

Fileter介紹

  • Filter也稱之爲過濾器,它是Servlet技術中最實用的技術,WEB開發人員通過Filter技術,對web服務器管理的所有web資源:例如Jsp, Servlet, 靜態圖片文件或靜態 html 文件等進行攔截,從而實現一些特殊的功能。例如實現URL級別的權限訪問控制、過濾敏感詞彙、壓縮響應信息等一些高級功能。

  • Servlet API中提供了一個Filter接口,開發web應用時,如果編寫的Java類實現了這個接口,則把這個java類稱之爲過濾器Filter。通過Filter技術,開發人員可以實現用戶在訪問某個目標資源之前,對訪問的請求和響應進行攔截。


Filter如何實現攔截

  • Filter接口中有一個doFilter方法,當開發人員編寫好Filter,並配置對哪個web資源(攔截url)進行攔截後,WEB服務器每次在調用web資源之前,都會先調用一下filter的doFilter方法,因此,在該方法內編寫代碼可達到如下目的:
    • 調用目標資源之前,讓一段代碼執行
    • 是否調用目標資源(即是否讓用戶訪問web資源)。
      • web服務器在調用doFilter方法時,會傳遞一個filterChain對象進來,filterChain對象是filter接口中最重要的一個對象,它也提供了一個doFilter方法,開發人員可以根據需求決定是否調用此方法,調用該方法,則web服務器就會調用web資源的service方法,即web資源就會被訪問,否則web資源不會被訪問。
    • 調用目標資源之後,讓一段代碼執行


開發Fileter步驟

  • Filter開發分爲三個步驟:
    • 編寫java類實現Filter接口,
    • 實現(三個方法)其doFilter方法。
    • 在 web.xml 文件中使用<filter><filter-mapping>元素對編寫的filter類進行註冊,並設置它所能攔截的資源。

Filter鏈 — FilterChain

  • 在一個web應用中,可以開發編寫多個Filter,這些Filter組合起來稱之爲一個Filter鏈

  • web服務器根據Filter在web.xml文件中的註冊順序<mapping>,決定先調用哪個Filter,當第一個Filter的doFilter方法被調用時,web服務器會創建一個代表Filter鏈的FilterChain對象傳遞給該方法。在doFilter方法中,開發人員如果調用了FilterChain對象的doFilter方法,則web服務器會檢查FilterChain對象中是否還有filter,如果有,則調用第2個filter,如果沒有,則調用目標資源。

  • Filter鏈實驗(查看filterChain API文檔)




Filter的生命週期

  • init(FilterConfig filterConfig)throws ServletException

    • 和我們編寫的Servlet程序一樣,Filter的創建和銷燬由WEB服務器負責。 web 應用程序啓動時,web 服務器將創建Filter 的實例對象,並調用其init方法進行初始化(注:filter對象只會創建一次,init方法也只會執行一次。示例 )
    • 開發人員通過init方法的參數,可獲得代表當前filter配置信息的FilterConfig對象。(filterConfig對象見下頁PPT)
  • doFilter(ServletRequest,ServletResponse,FilterChain)

    • 每次filter進行攔截都會執行
    • 在實際開發中方法中參數request和response通常轉換爲HttpServletRequest和HttpServletResponse類型進行操作
  • destroy()

    • 在Web容器卸載 Filter 對象之前被調用。




FilterConfig接口

  • 用戶在配置filter時,可以使用<init-param>爲filter配置一些初始化參數,當web容器實例化Filter對象,調用其init方法時,會把封裝了filter初始化參數的filterConfig對象傳遞進來。因此開發人員在編寫filter時,通過filterConfig對象的方法,就可獲得:
    • String getFilterName():得到filter的名稱
    • String getInitParameter(String name): 返回在部署描述中指定名稱的初始化參數的值。如果不存在返回null。
    • Enumeration getInitParameterNames():返回過濾器的所有初始化參數的名字的枚舉集合。
    • public ServletContext getServletContext():返回Servlet**上下文對象**的引用。


註冊與映射Filter

註冊

<filter>
         <filter-name>testFitler</filter-name>
         <filter-class>org.test.TestFiter</filter-class>
         <init-param>
             <param-name>word_file</param-name>    
             <param-value>/WEB-INF/word.txt</param-value>
         </init-param>
</filter>
  • <filter-name>用於爲過濾器指定一個名字,該元素的內容不能爲空。
  • <filter-class>元素用於指定過濾器的完整的限定類名。
  • <init-param>元素用於爲過濾器指定初始化參數,它的子元素<param-name>指定參數的名字
  • <param-value>指定參數的值。在過濾器中,可以使用FilterConfig接口對象來訪問初始化參數。




映射Filter

  • 映射Filter示例
<filter-mapping>
    <filter-name>testFilter</filter-name>
   <url-pattern>/index.jsp</url-pattern>
   <dispatcher>REQUEST</dispatcher>
   <dispatcher>FORWARD</dispatcher>
</filter-mapping>
  • <filter-mapping>元素用於設置一個 Filter 所負責攔截的資源。一個Filter攔截的資源可通過兩種方式來指定:Servlet 名稱和資源訪問的請求路徑
  • <filter-name>子元素用於設置filter的註冊名稱。該值必須是在<filter>元素中聲明過的過濾器的名字

  • <url-pattern>設置 filter 所攔截的請求路徑(過濾器關聯的URL樣式)

    • 1 . 完全匹配 必須以/開始。
    • 2 . 可以使用*通配符。
      • 目錄匹配 /a/*,/* 要求必須以/開始。
      • 擴展名匹配 *.do,*.action 要求,不能以/開始,以*.xxx結束。
  • <servlet-name>指定過濾器所攔截的Servlet名稱。

  • <dispatcher>指定過濾器所攔截的資源被 Servlet 容器調用的方式,可以是REQUEST,INCLUDE,FORWARDERROR之一,默認REQUEST。用戶可以設置多個<dispatcher> 子元素用來指定 Filter 對資源的多種調用方式進行攔截。

  • <dispatcher> 子元素可以設置的值及其意義:

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




Filter示例

示例1:全站統一字符編碼過濾器

  • 通過配置參數encoding指明使用何種字符編碼,以處理Html Form請求參數的中文問題

  • 編寫jsp 輸入用戶名,在Servlet中獲取用戶名,將用戶名輸出到瀏覽器上

  • 處理請求post亂碼代碼

    • request.setCharacterEncoding(“utf-8”);
  • 設置響應編碼集代碼

    • response.setContentType(“text/html;charset=utf-8”);
  • 經常會使用,而過濾器可以在目標資源之前執行,將很多程序中處理亂碼公共代碼,提取到過濾器中,以後程序中不需要處理編碼問題了

public class EncodingFilter implements Filter {
    private String encode;
    public void destroy() {
    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain chain) throws IOException, ServletException {

        // 1.強制轉換
        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;

        // 2.操作
        request.setCharacterEncoding(encode);
        // 3.放行
        chain.doFilter(request, response);

    }

    public void init(FilterConfig config) throws ServletException {
        this.encode = config.getInitParameter("encode");
    }

}



    <!-- post編碼過濾器 -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>cn.itcast.filter.demo1.EncodingFilter</filter-class>

        <init-param>
            <param-name>encode</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


示例2禁用所有JSP頁面緩存

  • 因爲動態頁面數據,是由程序生成的,所以如果有緩存,就會發生,客戶端查看數據不是最新數據情況 ,對於動態程序生成頁面,設置瀏覽器端禁止緩存頁面內容

  • 有 3 個 HTTP 響應頭字段都可以禁止瀏覽器緩存當前頁面,它們在 Servlet 中的示例代碼如下:

    • response.setDateHeader(“Expires”,-1);
    • response.setHeader(“Cache-Control”,”no-cache”);
    • response.setHeader(“Pragma”,”no-cache”);
  • 並不是所有的瀏覽器都能完全支持上面的三個響應頭,因此最好是同時使用上面的三個響應頭。

  • Expires數據頭:值爲GMT時間值,爲-1指瀏覽器不要緩存頁面
  • Cache-Control響應頭有兩個常用值:

    • no-cache指瀏覽器不要緩存當前頁面。
    • max-age:xxx指瀏覽器緩存頁面xxx秒。
  • 將禁用緩存代碼,提起到過濾器中,通過url配置,禁用所有JSP頁面的緩存

public class CacheFilter implements Filter{

    public void destroy() {

    }

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {

        // 1.強制轉換
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        // 2.操作

        response.setHeader("pragma", "no-cache");
        response.setHeader("cache-control", "no-cache");
        response.setDateHeader("expires", 0);

        // 3.放行
        chain.doFilter(request, response);
    }


    public void init(FilterConfig filterConfig) throws ServletException {


    }


}


    <filter>
        <filter-name>cacheFilter</filter-name>
        <filter-class>cn.itcast.filter.demo2.CacheFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>cacheFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
    </filter-mapping>

示例3設置圖片過期時間

  • 控制瀏覽器緩存頁面中的靜態資源的過濾器:
  • 場景:有些動態頁面中引用了一些圖片或css文件以修飾頁面效果,這些圖片和css文件經常是不變化的,所以爲減輕服務器的壓力,可以使用filter控制瀏覽器緩存這些文件,以提升服務器的性能。

  • Tomcat緩存策略

  • 對於服務器端經常不變化文件,設置客戶端緩存時間,在客戶端資源緩存時間到期之前,就不會去訪問服務器獲取該資源 ——– 比tomcat內置緩存策略更優手段

  • 減少服務器請求次數,提升性能

  • 設置靜態資源緩存時間,需要設置 Expires 過期時間 ,在客戶端資源沒有過期之前,不會產生對該資源的請求的

  • 設置Expires 通常使用 response.setDateHeader 進行設置 設置毫秒值

public class ImageCacheFilter implements Filter {

    public void destroy() {

    }

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        // 1.強制轉換
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        // 2.操作    
        response.setDateHeader("expires", System.currentTimeMillis()+60*60*24*10*1000);//緩存10天

        // 3.放行
        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {

    }

}



    <filter>
        <filter-name>cacheFilter</filter-name>
        <filter-class>cn.itcast.filter.demo2.CacheFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>imageFilter</filter-name>
        <url-pattern>*.bmp</url-pattern>
    </filter-mapping>

示例4自動登錄案例(MD5加密)

  • 說明:在訪問一個站點,登陸時勾選自動登陸(三個月內不用登陸),操作系統後,關閉瀏覽器;過幾天再次訪問該站點時,直接進行登陸後狀態。
  • 要求:實現用戶自動登陸的過濾器

    • 在用戶登陸成功後,以cookis形式發送用戶名、密碼給客戶端
    • 編寫一個過濾器,filter方法中檢查cookie中是否帶有用戶名、密碼信息,如果存在則調用業務層登陸方法,登陸成功後則向session中存入user對象(即用戶登陸標記),以實現程序完成自動登陸。
  • 在數據庫中創建 user表

create table user (
   id int primary key auto_increment,
   username varchar(20),
   password varchar(40),
   role varchar(10)
);

insert into user values(null,'admin','123','admin');
insert into user values(null,'aaa','123','user');
insert into user values(null,'bbb','123','user');
  • 完成自動登錄原理:
  • 在用戶完成登陸後,勾選自動登陸複選框,服務器端將用戶名和密碼 以Cookie形式,保存在客戶端 。當用戶下次訪問該站點,AutoLoginFilter 過濾器從Cookie中獲取 自動登陸信息

    • 1、登錄成功後,判斷是否勾選了自動登錄。
    • 2、如果勾線了自動登錄,將用戶名與密碼儲存到cookie中。
    • 3、做一個Filter,它攔截所有請求,當訪問資源時,我們從cookie中獲取用戶名與密碼,進行登錄操作。
  • 在LoginServlet中主要工作

    • 如果登錄成功後,判斷是否勾選了自動登錄,如果勾選了,將用戶名與密碼存儲到cookie中。
if("ok".equals(autologin)){
                    Cookie cookie = new Cookie("autologin",URLEncoder.encode(username,"utf-8")+"::"+password);
                    cookie.setMaxAge(60*60*24*10);
                    cookie.setPath("/");
                    response.addCookie(cookie);
                }
  • 創建一個AutoLoginFilter進行自動登錄操作
    • 得到autologin這個cookie
      • Cookie cookie = CookieUtils.findCookieByName(request.getCookies(),"autologin");
    • 得到username與password進行登錄操作
    if(cookie != null){
                    String username = URLDecoder.decode(cookie.getValue().split("::")[0],"utf-8");
                    String password = cookie.getValue().split("::")[1];

                    UserService service = new UserService();
                    User user;

                    try {
                        user = service.login(username, password);
                        if(user != null){
                            request.getSession().setAttribute("user", user);
                             response.sendRedirect(request.getContextPath()
                             + "/demo4/success.jsp");
                            return;
                        }
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }


  • 如果將用戶密碼保存在cookie文件中,非常不安全的 ,通常情況下密碼需要加密後才能保存到客戶端

    • 使用md5算法對密碼進行加密
    • md5 加密算法是一個單向加密算法 ,支持明文—密文 不支持密文解密
  • MySQL數據庫中提供md5 函數,可以完成md5 加密

    • mysql> select md5(‘123’);
    • 將數據表中所有密碼 變爲密文 update user set password = md5(password) ;
  • Java中提供類 MessageDigest 完成MD5加密

  • MD5加密算法
       /**
     * 使用md5的算法進行加密
     */
    public static String md5(String plainText) {
        byte[] secretBytes = null;
        try {
            secretBytes = MessageDigest.getInstance("md5").digest(
                    plainText.getBytes());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("沒有md5這個算法!");
        }
        String md5code = new BigInteger(1, secretBytes).toString(16);
        for (int i = 0; i < 32 - md5code.length(); i++) {
            md5code = "0" + md5code;
        }
        return md5code;


示例5 URL級別的權限控制

  • 使用Filter實現URL級別的權限認證

  • 情景:在實際開發中我們經常把一些執行敏感操作的servlet映射到一些特殊目錄中,並用filter把這些特殊目錄保護起來,限制只能擁有相應訪問權限的用戶才能訪問這些目錄下的資源。從而在我們系統中實現一種URL級別的權限功能。

  • 要求:爲使Filter具有通用性,Filter保護的資源和相應的訪問權限通過filter參數的形式予以配置。

  • 問題1:怎樣判斷哪些資源需要權限,哪些資源不需要權限?

//admin.properites
url=/book_add,/book_delete,/book_update,/book_search


//user.properites
url=/book_search

        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String path = uri .substring(contextPath.length());

        if(admins.contains(path) || users.contains(path)){
            User user = (User) request.getSession().getAttribute("user");

            if(user == null){
                throw new PrivilegeException();
            }



public void init(FilterConfig arg0) throws ServletException {
        this.admins = new ArrayList<String>();
        this.users = new ArrayList<String>();

        fillPath("user",users);
        fillPath("admin",admins);
    }

    private void fillPath(String baseName,List<String>list){
        ResourceBundle bundle = ResourceBundle.getBundle(baseName);

        String path = bundle.getString("url");

        String[] paths = path.split(",");

        for(String p : paths){
            list.add(p);
        }
    }            
  • 問題2:我們的用戶是有role的,如果是admin角色,如何限制權限,如果是user角色如何限制權限?
if("admin".equals(user.getRole())){
                if(!(admins.contains(path))){
                    throw new PrivilegeException();
                }
            }
            else {
                if(!(users.contains(path))){
                    throw new PrivilegeException();
                }
            }

示例6 通用get和post亂碼過濾器

  • 由於開發人員在filter中可以得到代表用戶請求和響應的request、response對象,因此在編程中可以使用Decorator(裝飾器)模式對request、response對象進行包裝,再把包裝對象傳給目標資源,從而實現一些特殊需求。

  • Decorator模式
  • 1、包裝類需要和被包裝對象 實現相同接口,或者繼承相同父類
  • 2、包裝類需要持有 被包裝對象的引用
    • 在包裝類中定義成員變量,通過包裝類構造方法,傳入被包裝對象
  • 3、在包裝類中,可以控制原來那些方法需要加強不需要加強 ,調用被包裝對象的方法需要加強,編寫增強代碼邏輯


  • Decorator設計模式的實現
    • 1.首先看需要被增強對象繼承了什麼接口或父類,編寫一個類也去繼承這些接口或父類。
    • 2.在類中定義一個變量,變量類型即需增強對象的類型。
    • 3.在類中定義一個構造函數,接收需增強的對象。
    • 4.覆蓋需增強的方法,編寫增強的代碼。


request對象的增強
  • Servlet API 中提供了一個request對象的Decorator設計模式的默認實現類HttpServletRequestWrapper

    • HttpServletRequestWrapper 類實現了request 接口中的所有方法,但這些方法的內部實現都是僅僅調用了一下所包裝的的 request 對象的對應方法,以避免用戶在對request對象進行增強時需要實現request接口中的所有方法。
  • 使用Decorator模式包裝request對象,完全解決get、post請求方式下的亂碼問題

  • ServletRequestWrapperHttpServletRequestWrapper 提供對request對象進行包裝的方法,但是默認情況下每個方法都是調用原來request對象的方法,也就是說包裝類並沒有對request進行增強

  • 在這兩個包裝類基礎上,繼承HttpServletRequestWrapper ,覆蓋需要增強的方法即可

  • 系統中存在很多資源,將需要進行權限控制的資源,放入特殊路徑中,編寫過濾器管理訪問特殊路徑的請求,如果沒有相應身份和權限,控制無法訪問

  • 在Filter中,對request對象進行包裝,增強獲得參數的方法

    • getParameter
    • getParameterValues
    • getParameterMap


public class EncodingFilter implements Filter{

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        HttpServletRequest myrequest = new MyRequest(request);
        response.setContentType("text/html;charset=utf-8");

        chain.doFilter(myrequest, response);
    }


    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}


class MyRequest extends HttpServletRequestWrapper{
    private HttpServletRequest request;

    public MyRequest(HttpServletRequest request){
        super(request);
        this.request=request;
    }

    public String getParameter(String name){
        Map<String,String[]> map = getParameterMap();

        if(name == null){
            return null;
        }
        String[] st = map.get(name);
        if(st ==null || st.length==0){
            return null;
        }

        return st[0];
    }

    private boolean flag = true;

    public Map getParaMap(){
        Map<String,String[]> map = request.getParameterMap();

        if(flag){
            for(String key : map.keySet()){
                String[] values = map.get(key);

                for(int i = 0;i<values.length;i++){
                    try {
                        values[i] = new String(values[i].getBytes("iso8859-1"),"utf-8");
                    } catch (UnsupportedEncodingException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            flag = false;
        }
        return map;

    }
}
response對象的增強
  • Servlet API 中提供了response對象的Decorator設計模式的默認實現類HttpServletResponseWrapper , (HttpServletResponseWrapper類實現了response接口中的所有方法,但這些方法的內部實現都是僅僅調用了一下所包裝的的 response對象的對應方法)以避免用戶在對response對象進行增強時需要實現response接口中的所有方法。

  • ServletResponseWrapperHttpServletResponseWrapper 提供了對response 對象包裝,繼承 HttpServletResponseWrapper ,覆蓋需要增強response的方法

  • response增強案例—壓縮響應

    • 應用HttpServletResponseWrapper對象,壓縮響應正文內容。
    • 思路:
      • 通過filter向目標頁面傳遞一個自定義的response對象。
        • 在自定義的response對象中,重寫getOutputStream方法和getWriter方法,使目標資源調用此方法輸出頁面內容時,獲得的是我們自定義的ServletOutputStream對象。
        • 在我們自定義的ServletOuputStream對象中,重寫write方法,使寫出的數據寫出到一個buffer中。
      • 當頁面完成輸出後,在filter中就可得到頁面寫出的數據,從而我們可以調用GzipOuputStream對數據進行壓縮後再寫出給瀏覽器,以此完成響應正文件壓縮功能。
發佈了91 篇原創文章 · 獲贊 177 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章