我在上海樂位元組學習的第二十一天(持續更新中)

weixin_51578167發表於2020-11-15

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伺服器在呼叫doFilter方法時,會傳遞一個filterChain物件進來,filterChain物件是filter介面中最重要的一個物件,它也提供了一個doFilter方法,開發人員可以根據需求決定是否呼叫此方法,呼叫該方法,則web伺服器就會呼叫web資源的service方法,即web資源就會被訪問,否則web資源不會被訪問。

呼叫目標資源之前,讓一段程式碼執行

是否呼叫目標資源(即是否讓使用者訪問web資源)。

呼叫目標資源之後,讓一段程式碼執行

開發Fileter步驟

Filter開發分為三個步驟:

編寫java類實現Filter介面,

實現(三個方法)其doFilter方法。

在 web.xml 檔案中使用和元素對編寫的filter類進行註冊,並設定它所能攔截的資源。

Filter鏈 — FilterChain

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

web伺服器根據Filter在web.xml檔案中的註冊順序,決定先呼叫哪個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時,可以使用

為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>

用於為過濾器指定一個名字,該元素的內容不能為空。

元素用於指定過濾器的完整的限定類名。

元素用於為過濾器指定初始化引數,它的子元素指定引數的名字

指定引數的值。在過濾器中,可以使用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 所負責攔截的資源。一個Filter攔截的資源可通過兩種方式來指定:Servlet 名稱和資源訪問的請求路徑

子元素用於設定filter的註冊名稱。該值必須是在元素中宣告過的過濾器的名字

設定 filter 所攔截的請求路徑(過濾器關聯的URL樣式)

目錄匹配 /a/,/ 要求必須以/開始。

副檔名匹配 .do,.action 要求,不能以/開始,以*.xxx結束。

1 . 完全匹配 必須以/開始。

2 . 可以使用

萬用字元。

指定過濾器所攔截的Servlet名稱。

指定過濾器所攔截的資源被 Servlet 容器呼叫的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,預設REQUEST。使用者可以設定多個 子元素用來指定 Filter 對資源的多種呼叫方式進行攔截。

子元素可以設定的值及其意義:

REQUEST:當使用者直接訪問頁面時,Web容器將會呼叫過濾器。如果目標資源是通過 RequestDispatcher 的 include()或 forward()方法訪問時,那麼該過濾器就不會被呼叫。

INCLUDE:如果目標資源是通過 RequestDispatcher 的 include() 方法訪問時,那麼該過濾器將被呼叫。除此之外,該過濾器不會被呼叫。

FORWARD:如果目標資源是通過 RequestDispatcher 的 forward()方法訪問時,那麼該過濾器將被呼叫,除此之外,該過濾器不會被呼叫。

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進行自動登入操作

Cookie cookie = CookieUtils.findCookieByName(request.getCookies(),“autologin”);

得到autologin這個cookie

得到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請求方式下的亂碼問題

ServletRequestWrapper 和 HttpServletRequestWrapper 提供對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介面中的所有方法。

ServletResponseWrapper 和HttpServletResponseWrapper 提供了對response 物件包裝,繼承 HttpServletResponseWrapper ,覆蓋需要增強response的方法

response增強案例—壓縮響應

通過filter向目標頁面傳遞一個自定義的response物件。

當頁面完成輸出後,在filter中就可得到頁面寫出的資料,從而我們可以呼叫GzipOuputStream對資料進行壓縮後再寫出給瀏覽器,以此完成響應正檔案壓縮功能。

在自定義的response物件中,重寫getOutputStream方法和getWriter方法,使目標資源呼叫此方法輸出頁面內容時,獲得的是我們自定義的ServletOutputStream物件。

在我們自定義的ServletOuputStream物件中,重寫write方法,使寫出的資料寫出到一個buffer中。

應用HttpServletResponseWrapper物件,壓縮響應正文內容。

相關文章