Java Web之Filter

本文包括:

1、Filter簡(jiǎn)介

2、Filter是如何實(shí)現(xiàn)攔截的?

3、Filter開發(fā)入門

4、Filter的生命周期

5、FilterConfig接口

6、配置Filter總結(jié)

7、案例一:編碼集統(tǒng)一處理

8、案例二:禁用緩存過濾器

9、案例三:高效的靜態(tài)資源緩存過濾器

10、案例四:實(shí)現(xiàn)用戶自動(dòng)登陸的過濾器

11、案例五:使用Filter實(shí)現(xiàn)URL級(jí)別的權(quán)限認(rèn)證

1、Filter簡(jiǎn)介

  • Filter也稱之為過濾器,它是Servlet技術(shù)中最實(shí)用的技術(shù),WEB開發(fā)人員通過Filter技術(shù),對(duì)web服務(wù)器管理的所有web資源:例如Jsp,Servlet,靜態(tài)圖片文件或靜態(tài)HTML文件等進(jìn)行攔截,從而實(shí)現(xiàn)一些特殊的功能。

  • 例如實(shí)現(xiàn)URL級(jí)別的權(quán)限訪問控制(最常用)、過濾敏感詞匯、壓縮響應(yīng)信息等一些高級(jí)功能。

  • Servlet的API中提供了一個(gè)Filter接口,開發(fā)web應(yīng)用時(shí),如果編寫的Java類實(shí)現(xiàn)了這個(gè)接口,則把這個(gè)java類稱之為過濾器Filter。通過Filter技術(shù),開發(fā)人員可以實(shí)現(xiàn)用戶在訪問某個(gè)目標(biāo)資源之前,對(duì)訪問的請(qǐng)求和響應(yīng)進(jìn)行攔截,如下所示:


2、Filter是如何實(shí)現(xiàn)攔截的?

  • Filter接口中有一個(gè)doFilter方法,當(dāng)開發(fā)人員編寫好Filter,并配置對(duì)哪個(gè)web資源(對(duì)應(yīng)的URL)進(jìn)行攔截后,WEB服務(wù)器每次在調(diào)用web資源之前,都會(huì)先調(diào)用一下filter的doFilter方法,因此,在Filter.doFilter()方法內(nèi)編寫代碼一般分為三個(gè)步驟:

    1. 調(diào)用目標(biāo)資源之前,讓一段代碼執(zhí)行

    2. 是否調(diào)用目標(biāo)資源(即是否讓用戶訪問web資源)。

      • web服務(wù)器在調(diào)用doFilter方法時(shí),會(huì)傳遞一個(gè)filterChain對(duì)象進(jìn)來(lái),filterChain對(duì)象是filter接口中最重要的一個(gè)對(duì)象,它也提供了一個(gè)doFilter方法,開發(fā)人員可以根據(jù)需求決定是否調(diào)用此方法。如果不調(diào)用FilterChain.doFilter()方法,即默認(rèn)情況下,如下所示:

          @Override
          public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws IOException, ServletException {
        
              System.out.println("filter doFilter...");
              // 默認(rèn):對(duì)目標(biāo)資源進(jìn)行了攔截,目標(biāo)資源沒有被訪問
          }
        
      • 若開發(fā)人員調(diào)用filterChain的doFilter方法,則web服務(wù)器就會(huì)調(diào)用web資源的service方法,即web資源就會(huì)被訪問,否則web資源不會(huì)被訪問,如下所示:

          @Override
          public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws IOException, ServletException {
              System.out.println("filter doFilter...");
              // 默認(rèn):對(duì)目標(biāo)資源進(jìn)行了攔截,目標(biāo)資源沒有被訪問
        
              // 如果訪問請(qǐng)求調(diào)用鏈下一個(gè)資源,這樣就不會(huì)攔截該web資源
              chain.doFilter(request, response);
          }
        
    3. 調(diào)用目標(biāo)資源之后,讓一段代碼執(zhí)行

3、Filter開發(fā)入門

  • Filter開發(fā)分為三個(gè)步驟:

    • 編寫java類實(shí)現(xiàn)(implement)Filter接口,并重寫(override)其doFilter方法(也可以同時(shí)重寫init方法與destroy方法)。

    • 在 web.xml 文件中使用<filter><filter-mapping>元素對(duì)編寫的filter類進(jìn)行注冊(cè),并設(shè)置它所能攔截的資源,代碼如下所示:

          <!-- 對(duì)過濾器注冊(cè),為過濾器定義 name -->
          <filter>
              <filter-name>Filter1</filter-name>
              <filter-class>cn.itcast.filter.Filter1</filter-class>
          </filter>    
        
            <!-- 配置過濾器攔截哪個(gè)web 資源 -->
          <filter-mapping>
              <filter-name>Filter1</filter-name>
              <!-- 攔截路徑 -->
              <url-pattern>/hello.jsp</url-pattern>
          </filter-mapping>
      

      其實(shí)這與在web.xml文件中配置Servlet是一個(gè)道理:

          <servlet>
            <servlet-name>HelloServlet</servlet-name>
            <servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
          </servlet>
        
         <servlet-mapping>
            <servlet-name>HelloServlet</servlet-name>
            <url-pattern>/hello</url-pattern>
          </servlet-mapping>
      
    • 在java類中覆蓋init、doFilter、destroy方法。

    Filter開發(fā)步驟與Servlet開發(fā)步驟如出一轍,下面是Servlet的開發(fā)步驟:

    1、繼承HttpServlet

    2、配置web.xml Servlet虛擬路徑

    3、覆蓋doGet 和 doPost

    • 代碼如下:

        public class Filter1 implements Filter {
            public Filter1() {
                System.out.println("創(chuàng)建了Filter1實(shí)例");
            }
        
            @Override
            public void destroy() {
                System.out.println("filter destroy...");
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                System.out.println("filter doFilter...");
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                System.out.println("filter init...");
            }
        
        }
      
  • Filter鏈 --- FilterChain(難點(diǎn)

    • 在一個(gè)web應(yīng)用中,可以開發(fā)編寫多個(gè)Filter,這些Filter組合起來(lái)稱之為一個(gè)Filter鏈。

    • web服務(wù)器根據(jù)多個(gè)Filter在web.xml文件中的注冊(cè)順序<mapping>,決定先調(diào)用哪個(gè)Filter,當(dāng)?shù)谝粋€(gè)Filter的doFilter方法被調(diào)用時(shí),web服務(wù)器會(huì)創(chuàng)建一個(gè)代表Filter鏈的FilterChain對(duì)象傳遞給該方法。(詳見API文檔)

    • 在Filter.doFilter()方法中,開發(fā)人員如果調(diào)用了FilterChain對(duì)象的doFilter方法,則web服務(wù)器會(huì)檢查FilterChain對(duì)象中是否還有filter,如果有,則調(diào)用第2個(gè)filter,如果沒有,則調(diào)用目標(biāo)資源。

    • demo:

         <filter>
              <filter-name>Filter1</filter-name>
              <filter-class>cn.itcast.filter.Filter1</filter-class>
          </filter>    
                        
          <filter-mapping>
              <filter-name>Filter1</filter-name>
              <url-pattern>/hello.jsp</url-pattern>
          </filter-mapping>
      
          <filter>
              <filter-name>Filter2</filter-name>
              <filter-class>cn.itcast.filter.Filter2</filter-class>
              </init-param>
          </filter>
          
          <filter-mapping>
              <filter-name>Filter2</filter-name>
              <url-pattern>/hello.jsp</url-pattern>
          </filter-mapping>
      

4、Filter的生命周期

  • init(FilterConfig filterConfig)throws ServletException:

    • 和我們編寫的Servlet程序一樣,F(xiàn)ilter的創(chuàng)建和銷毀由WEB服務(wù)器負(fù)責(zé)。 web 應(yīng)用程序啟動(dòng)時(shí),web 服務(wù)器將創(chuàng)建Filter 的實(shí)例對(duì)象,并調(diào)用其init方法進(jìn)行初始化(注:filter對(duì)象只會(huì)創(chuàng)建一次,init方法也只會(huì)執(zhí)行一次)

      注意:在初始化(init)階段,Servlet不一樣,當(dāng)?shù)谝淮卧L問Servlet的時(shí)候web服務(wù)器才會(huì)創(chuàng)建Servlet的實(shí)例對(duì)象。

    • 開發(fā)人員通過init方法的參數(shù),可獲得代表當(dāng)前filter配置信息的FilterConfig對(duì)象。(filterConfig對(duì)象見下文)

  • doFilter(ServletRequest,ServletResponse,FilterChain)

    • 每次filter進(jìn)行攔截都會(huì)執(zhí)行(每請(qǐng)求一次,執(zhí)行一次)

    • 在實(shí)際開發(fā)中方法中參數(shù)request和response通常轉(zhuǎn)換為HttpServletRequest和HttpServletResponse類型進(jìn)行操作

  • destroy():

    • 在Web容器卸載 Filter 對(duì)象之前被調(diào)用。

5、FilterConfig接口

  • 在前文中,重寫init方法時(shí),觀察到init方法需要FilterConfig對(duì)象的參數(shù),接下來(lái)就談?wù)凢ilterConfig這個(gè)接口。

  • 用戶在配置filter時(shí),可以使用<init-param>為filter配置一些初始化參數(shù),當(dāng)web容器實(shí)例化Filter對(duì)象,調(diào)用其init方法時(shí),會(huì)把封裝了filter初始化參數(shù)的filterConfig對(duì)象傳遞進(jìn)來(lái)。因此開發(fā)人員在編寫filter時(shí),通過filterConfig對(duì)象可調(diào)用如下方法:

    • String getFilterName():得到filter的名稱。

    • String getInitParameter(String name): 返回在部署描述中指定名稱的初始化參數(shù)的值,如果不存在返回null。

    • Enumeration getInitParameterNames():返回過濾器的所有初始化參數(shù)的名字的枚舉集合。

    • public ServletContext getServletContext():返回Servlet上下文對(duì)象的引用。

      關(guān)于ServletContext對(duì)象的用處可參考本人之前的一篇文章:Java Web 之 Servlet

  • demo:

    • 配置web.xml文件:

          <filter>
              <filter-name>Filter2</filter-name>
              <filter-class>cn.itcast.filter.Filter2</filter-class>
              <!-- 為過濾器配置初始化參數(shù) -->
              <init-param>
                  <param-name>company</param-name>
                  <param-value>谷歌</param-value>
              </init-param>
          </filter>
          
          <filter-mapping>
              <filter-name>Filter2</filter-name>
              <url-pattern>/hello.jsp</url-pattern>
          </filter-mapping>
      
    • 實(shí)現(xiàn)Filter接口的自定義測(cè)試類:

        public class Filter2 implements Filter {
        
            @Override
            public void destroy() {
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                System.out.println("filter2 doFilter...");
                chain.doFilter(request, response);
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                // 功能一 :獲得配置初始化參數(shù)
                String company = filterConfig.getInitParameter("company");
                System.out.println(company);
                // 功能二 :讀取web資源文件
                ServletContext servletContext = filterConfig.getServletContext();
                // 讀取web資源文件 必須獲得絕對(duì)磁盤路徑
                String filePath = servletContext.getRealPath("/WEB-INF/info.txt"); 
                try {
                    BufferedReader reader = new BufferedReader(new FileReader(filePath));
                    System.out.println(reader.readLine());
                    reader.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        
        }
      

6、配置Filter總結(jié)

  1. 對(duì)一個(gè)web資源可以配置多個(gè)過濾器

  2. 一個(gè)過濾器可以用來(lái)過濾多個(gè)web 資源

  3. <filter-mapping>中若想過濾Servlet時(shí),可以通過Servlet對(duì)應(yīng)的URL 或 Servlet的名字 兩種方式配置:

    • Servlet對(duì)應(yīng)的URL:

          <filter-mapping>
              <filter-name>Filter1</filter-name>
              <servlet-name>HelloServlet</servlet-name>
          </filter-mapping>
      
    • Servlet的名字:

          <filter-mapping>
              <filter-name>Filter1</filter-name>
            <url-pattern>/hello</url-pattern>
          </filter-mapping>
      
    • 測(cè)試用的Servlet配置如下:

          <servlet>
            <servlet-name>HelloServlet</servlet-name>
            <servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
          </servlet>
        
          <servlet-mapping>
            <servlet-name>HelloServlet</servlet-name>
            <url-pattern>/hello</url-pattern>
          </servlet-mapping>
      
  4. 關(guān)于<url-pattern>的寫法Filter和Servlet相同,有三種方式,完全匹配、目錄匹配、擴(kuò)展名匹配,詳解見:Java Web 之 Servlet

  5. <filter-mapping>中提供了一個(gè)可選的標(biāo)簽<dispatcher>

    • <dispatcher>標(biāo)簽指定過濾器所攔截的資源被 Servlet 容器調(diào)用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一。默認(rèn)REQUEST會(huì)過濾,其他三種方式都不會(huì)過濾。

      • request ---在請(qǐng)求時(shí)過濾

      • forward ---在轉(zhuǎn)發(fā)時(shí)過濾

      • include ---在包含時(shí)過濾

      • error --在錯(cuò)誤頁(yè)面跳轉(zhuǎn)時(shí)過濾

    • 用戶可以設(shè)置多個(gè)<dispatcher> 子元素用來(lái)指定 Filter 對(duì)資源的多種調(diào)用方式進(jìn)行攔截。

    • demo:

          <filter-mapping>
              <filter-name>Filter1</filter-name>
              <servlet-name>HelloServlet</servlet-name>
              <!-- 在轉(zhuǎn)發(fā)以及請(qǐng)求時(shí)進(jìn)行過濾 -->
              <dispatcher>FORWARD</dispatcher>
              <dispatcher>REQUEST</dispatcher>
          </filter-mapping>
      

7、案例一:編碼集統(tǒng)一處理

  • 假設(shè)有一個(gè)功能:input.jsp 提供輸入姓名的表單,提交表單后在Servlet獲得表單數(shù)據(jù),將數(shù)據(jù)打印頁(yè)面上。

  • 在Filter知識(shí)之前,我們通常要在Servlet的doPost方法中這樣做:

      request.setCharacterEncoding("utf-8");// 解決請(qǐng)求post亂碼
      response.setContentType("text/html;charset=utf-8"); // 解決響應(yīng)響應(yīng)亂碼
    
  • 這兩行代碼對(duì)于Java Web開發(fā)人員來(lái)說那是再熟悉不過了,很多Servlet中都需要這兩行,那可不可以簡(jiǎn)化一下呢?

  • 分析:Filter在目標(biāo)資源之前執(zhí)行,而且Filter可以攔截多個(gè)目標(biāo)資源,所以:多個(gè)目標(biāo)資源(多個(gè)Servlet)中相同代碼,可以抽取到Filter對(duì)象的doFilter方法中:

      public class EncodingFilter implements Filter {
      
          @Override
          public void destroy() {
          }
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws IOException, ServletException {
              // 設(shè)置請(qǐng)求和響應(yīng)字符集
              request.setCharacterEncoding("utf-8");// 解決請(qǐng)求post亂碼
              response.setContentType("text/html;charset=utf-8"); // 響應(yīng)亂碼
      
              // 目標(biāo)資源(Servlet)是可訪問的,不能被真正過濾掉
              chain.doFilter(request, response);
          }
      
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
          }
      
      }
    

    在web.xml中配置這個(gè)Filter:

        <!-- 配置全站編碼過濾器 -->
        <filter>
            <filter-name>EncodingFilter</filter-name>
            <filter-class>cn.itcast.demo1.EncodingFilter</filter-class>
        </filter>    
        <filter-mapping>
            <filter-name>EncodingFilter</filter-name>
          <!-- “過濾”該站所有URL資源 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
  • 至此,post請(qǐng)求的亂碼解決,get請(qǐng)求的亂碼解決方式有兩種:

    1. 配置tomcat server.xml中的URIEncoding="utf-8"

    2. new String(xxx.getBytes("ISO-8859-1"),"utf-8");

  • 同樣,還是仿照上文的思路,抽取相同代碼放入Filter中,可以解決全站get/post請(qǐng)求亂碼的問題,代碼如下(會(huì)用就行):

      package cn.itcast.filter;
      
      import java.io.IOException;
      import java.io.UnsupportedEncodingException;
      import java.util.Map;
      import java.util.Set;
      
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletRequestWrapper;
      /**
      * 解決亂碼通用的過濾器程序
      * @author seawind
      */
      public class EncodingFilter implements Filter {
      
          @Override
          public void destroy() {
          }
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws IOException, ServletException {
              // 解決post
              request.setCharacterEncoding("utf-8");
              // 解決get
              EncodingRequest encodingRequest = new EncodingRequest(
                      (HttpServletRequest) request);
              chain.doFilter(encodingRequest, response);
      
          }
      
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
          }
      
      }
      
      class EncodingRequest extends HttpServletRequestWrapper {
      
          private HttpServletRequest request;
      
          private boolean hasEncode = false;
      
          public EncodingRequest(HttpServletRequest request) {
              super(request);
              this.request = request;
          }
      
          @Override
          public String getParameter(String name) {
              // 通過getParameterMap方法完成
              String[] values = getParameterValues(name);
              if (values == null) {
                  return null;
              }
              return values[0];
          }
      
          @Override
          public String[] getParameterValues(String name) {
              // 通過getParameterMap方法完成
              Map<String, String[]> parameterMap = getParameterMap();
              String[] values = parameterMap.get(name);
              return values;
          }
      
          @Override
          public Map getParameterMap() {
              Map<String, String[]> parameterMap = request.getParameterMap();
              String method = request.getMethod();
              if (method.equalsIgnoreCase("post")) {
                  return parameterMap;
              }
      
              // get提交方式 手動(dòng)轉(zhuǎn)碼 , 這里的轉(zhuǎn)碼只進(jìn)行一次 所以通過 hasEncode 布爾類型變量控制
              if (!hasEncode) { 
                  Set<String> keys = parameterMap.keySet();
                  for (String key : keys) {
                      String[] values = parameterMap.get(key);
                      if (values == null) {
                          continue;
                      }
                      for (int i = 0; i < values.length; i++) {
                          String value = values[i];
                          // 解決get
                          try {
                              value = new String(value.getBytes("ISO-8859-1"),
                                      "utf-8");
                              // values是一個(gè)地址
                              values[i] = value;
                          } catch (UnsupportedEncodingException e) {
                              e.printStackTrace();
                          }
                      }
                      // 一次轉(zhuǎn)碼完成后,設(shè)置轉(zhuǎn)碼狀態(tài)為true
                      hasEncode = true;
                  }
              }
              return parameterMap;
          }
      }
    

8、案例二:禁用緩存過濾器

  • 分析:在web開發(fā)中,存在很多動(dòng)態(tài)web資源,經(jīng)常需要更新,為了保證客戶端第一時(shí)間獲得更新后的結(jié)果,應(yīng)該禁止動(dòng)態(tài)web資源的緩存。

  • 有 3 個(gè) HTTP 響應(yīng)頭字段都可以禁止瀏覽器緩存當(dāng)前頁(yè)面,它們?cè)?Servlet 中的示例代碼如下:

      cache-control : no-cache  ---- 指瀏覽器不要緩存當(dāng)前頁(yè)面。
      expires : -1  ---- 值為GMT時(shí)間值,為-1指瀏覽器不要緩存頁(yè)面
      pragma : no-cache
    

    cache-control : max-age (xxx指瀏覽器緩存頁(yè)面xxx秒)

    并不是所有的瀏覽器都能完全支持上面的三個(gè)響應(yīng)頭,因此最好是同時(shí)使用上面的三個(gè)響應(yīng)頭。

  • 在自定義Servlet測(cè)試類中添加三行代碼:

      public class ShowMessageServlet extends HttpServlet {
      
          public void doGet(HttpServletRequest request, HttpServletResponse response)
                  throws ServletException, IOException {
    
              // 禁止緩存
              response.setHeader("Cache-Control", "no-cache");
              response.setDateHeader("Expires", -1);
              response.setHeader("Pragma", "no-cache");
      
              response.getWriter().println("Message Servlet xxxx...");
          }
      
          public void doPost(HttpServletRequest request, HttpServletResponse response)
                  throws ServletException, IOException {
              doGet(request, response);
          }
      
      }
    
  • 仿照案例一的思路,可不可以抽取相同代碼放入Filter中呢?答案顯然是可以的:

    • web.xml配置Filter:

          <!-- 禁止動(dòng)態(tài)web資源緩存 -->
          <filter>
              <filter-name>NoCacheFilter</filter-name>
              <filter-class>cn.itcast.demo2.NoCacheFilter</filter-class>
          </filter>
          <filter-mapping>
              <filter-name>NoCacheFilter</filter-name>
              <!-- 禁用Servlet緩存 -->
              <url-pattern>/showmsg</url-pattern>
              <!-- 禁用所有JSP緩存 -->
              <url-pattern>*.jsp</url-pattern>
          </filter-mapping>
      
    • NoCacheFilter:

        public class NoCacheFilter implements Filter {
        
            @Override
            public void destroy() {
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                // 使用與HTTP協(xié)議相關(guān) API,需要將參數(shù)轉(zhuǎn)為子類型
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        
                httpServletResponse.setHeader("Cache-Control", "no-cache");
                httpServletResponse.setDateHeader("Expires", -1);
                httpServletResponse.setHeader("Pragma", "no-cache");
        
                chain.doFilter(request, httpServletResponse);
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
        
        }
      

9、案例三:高效的靜態(tài)資源緩存過濾器

  • 目標(biāo):控制瀏覽器緩存中的靜態(tài)資源的過濾器

  • 場(chǎng)景:有些動(dòng)態(tài)頁(yè)面中引用了一些圖片或css文件以修飾頁(yè)面效果,這些圖片和css文件經(jīng)常是不變化的,所以為減輕服務(wù)器的壓力,可以使用filter控制瀏覽器緩存這些文件,以提升服務(wù)器的性能。

  • Tomcat緩存機(jī)制:

  • 雖然Tomcat服務(wù)器內(nèi)部對(duì)靜態(tài)資源有一個(gè)緩存策略,但是客戶端仍然需要與服務(wù)器進(jìn)行通信,通信性能損失。如果一些圖片永遠(yuǎn)不變,可以通過Expires字段,設(shè)置圖片的緩存時(shí)間 (當(dāng)緩存時(shí)間沒有過期之前,客戶端訪問資源時(shí),不會(huì)與服務(wù)器進(jìn)行通信,這樣就減輕了服務(wù)器的壓力?。?/p>

  • Expires對(duì)于經(jīng)常不變文件,是一個(gè)比tomcat默認(rèn)緩存策略更優(yōu)的解決方案。

    • 設(shè)置Expires頭信息:

          <!-- 圖片過期時(shí)間過濾器 -->
          <filter>
              <filter-name>ImageExpiresFilter</filter-name>
              <filter-class>cn.itcast.demo3.ImageExpiresFilter</filter-class>
          </filter>
          <filter-mapping>
              <filter-name>ImageExpiresFilter</filter-name>
              <url-pattern>*.jpg</url-pattern>
              <url-pattern>*.bmp</url-pattern>
              <url-pattern>*.gif</url-pattern>
              <url-pattern>*.png</url-pattern>
          </filter-mapping>
      
    • ImageExpiresFilter:

        public class ImageExpiresFilter implements Filter {
        
            @Override
            public void destroy() {
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                // 設(shè)置圖片過期時(shí)間 ,設(shè)置Expires頭信息
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
      
                // 過期時(shí)間 = 當(dāng)前時(shí)間 + 還有多久過期                
                Calendar calendar = Calendar.getInstance();
                calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) + 1);
            
                httpServletResponse
                        .setDateHeader("Expires", calendar.getTimeInMillis()); // 過期時(shí)間一個(gè)月
        
                chain.doFilter(request, httpServletResponse);
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
        
        }
      

10、案例四:實(shí)現(xiàn)用戶自動(dòng)登陸的過濾器

  • 功能描述:在登陸界面,若用戶勾選了“自動(dòng)登錄”,則在以后的一段時(shí)間內(nèi)(可自行定義)再次訪問這個(gè)網(wǎng)站的某些網(wǎng)頁(yè)時(shí),不需要到登陸界面登陸,實(shí)現(xiàn)自動(dòng)登錄。

  • 編寫一個(gè)過濾器,filter方法中檢查cookie中是否帶有用戶名、密碼信息,如果存在則調(diào)用業(yè)務(wù)層登陸方法,登陸成功后則向session中存入user對(duì)象(即用戶登陸的標(biāo)識(shí)),以實(shí)現(xiàn)程序完成自動(dòng)登陸。

    基礎(chǔ)知識(shí)見:JSP 會(huì)話管理

  • “記住用戶名和密碼”的原理是利用cookie存儲(chǔ)user和password,“自動(dòng)登陸”的原理是利用過濾器得到cookie中存儲(chǔ)的user和password,直接進(jìn)入業(yè)務(wù)層登陸,得到user對(duì)象存入session中,即實(shí)現(xiàn)了自動(dòng)登陸。

  • 圖解:

  • 通過以上的分析,可以開始編寫代碼:

    • web.xml配置:

         <!-- 配置自動(dòng)登陸過濾器 -->
          <filter>
              <filter-name>AutoLoginFilter</filter-name>
              <filter-class>cn.itcast.demo4.AutoLoginFilter</filter-class>
          </filter>
          <filter-mapping>
              <filter-name>AutoLoginFilter</filter-name>
              <!-- 訪問welcome.jsp 時(shí)完成自動(dòng)登陸,最好別寫'/*', -->
              <url-pattern>/demo4/welcome.jsp</url-pattern>
          </filter-mapping>
      
    • CookieUtils:

        public class CookieUtils {
            // 在cookies 數(shù)組中 查找指定name 的cookie
            public static Cookie findCookie(Cookie[] cookies, String name) {
                if (cookies == null) {// cookie 不存在
                    return null;
                } else {
                    // cookie 存在
                    for (Cookie cookie : cookies) {
                        if (cookie.getName().equals(name)) {
                            // 找到
                            return cookie;
                        }
                    }
        
                    // 沒有找到
                    return null;
                }
            }
        }
      
    • AutoLoginFilter:

        public class AutoLoginFilter implements Filter {
        
            @Override
            public void destroy() {
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                // 1、判斷當(dāng)前用戶是否已經(jīng)登陸
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                if (httpServletRequest.getSession().getAttribute("user") == null) {
                    // 未登錄
                    // 2、判斷autologin對(duì)應(yīng)cookie 是否存在 ---- 將cookie 查詢寫為工具類
                    Cookie cookie = CookieUtils.findCookie(httpServletRequest
                            .getCookies(), "autologin");
                    if (cookie != null) {
                        // 找到了自動(dòng)登陸cookie
                        String username = cookie.getValue().split("\\.")[0];
                        // 如果用戶名中文,需要解密,詳情見下文
                        username = URLDecoder.decode(username, "utf-8");
        
                        String password = cookie.getValue().split("\\.")[1];
        
                        // 登陸邏輯
                        String sql = "select * from user where username=? and password = ?";
                        Object[] args = { username, password };
                        QueryRunner queryRunner = new QueryRunner(JDBCUtils
                                .getDataSource());
                        try {
                            User user = queryRunner.query(sql, new BeanHandler<User>(
                                    User.class), args);
                            if (user != null) {
                                httpServletRequest.getSession().setAttribute("user",
                                        user);
                            }
                            chain.doFilter(httpServletRequest, response);
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
        
                    } else {
                        // 沒有自動(dòng)登陸信息
                        chain.doFilter(httpServletRequest, response);
                    }
        
                } else {
                    // 已經(jīng)登陸,不需要自動(dòng)登陸
                    chain.doFilter(httpServletRequest, response);
                }
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
        
        }
      
    • LoginServlet

        public class LoginServlet extends HttpServlet {
        
            public void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                // 獲得用戶名和密碼
                String username = request.getParameter("username");
                String password = request.getParameter("password");
        
                // 查詢數(shù)據(jù)庫(kù)
                String sql = "select * from user where username = ? and password= ?";
                Object[] args = { username, password };
                QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
                try {
                    User user = queryRunner.query(sql,
                            new BeanHandler<User>(User.class), args);
                    if (user == null) {
                        // 查詢用戶不存在,登陸失敗
                        request.setAttribute("msg", "用戶名或者密碼錯(cuò)誤");
                        request.getRequestDispatcher("/demo4/login.jsp").forward(
                                request, response);
                    } else {
                        // 登陸成功
                        // 判斷是否勾選自動(dòng)登陸
                        if ("true".equals(request.getParameter("autologin"))) {
                            // 用戶勾選了 自動(dòng)登陸
                            // 將用戶名和密碼 以cookie形式寫給客戶端
                            Cookie cookie = new Cookie("autologin", URLEncoder.encode(
                                    username, "utf-8")
                                    + "." + password);
                            cookie.setMaxAge(60 * 60);
                            cookie.setPath("/day17");
                            response.addCookie(cookie);
                        }
        
                        // 將成功信息 保存Session
                        request.getSession().setAttribute("user", user); // 表示已經(jīng)登陸
                        response.sendRedirect("/day17/demo4/welcome.jsp");
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        
            public void doPost(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                doGet(request, response);
            }
        
        }
      
  • 自動(dòng)登陸案例問題:

    1. 如果用戶名中文怎么辦?

      解決方案:保存cookie時(shí)進(jìn)行手動(dòng)編碼 URL編碼

      • 生成cookie時(shí),new Cookie(URLEncoder.encode(用戶名,"utf-8")); ------ 寫在LoginServlet

      • 讀取cookie時(shí),URLDecoder.decode(用戶名,"utf-8"); ---- 寫在AutoLoginFilter

    2. 中文名和密碼安全問題

      • cookie 是一個(gè)文本類型文件,內(nèi)容非常容易泄露,實(shí)際開發(fā)中數(shù)據(jù)庫(kù)中密碼和cookie中密碼都應(yīng)該加密后保存 ---- 單向加密算法,如MD5

      • MySQL 可以將數(shù)據(jù)進(jìn)行md5 加密

         update user set password = md5(password) ;
        
      • Java也提供了MD5加密方式:

          public class DigestUtils {
          
              /**
               * 使用md5的算法進(jìn)行加密
               * @param plainText 加密原文
               * @return 加密密文
               */
              public static String md5(String plainText) {
                  byte[] secretBytes = null;
                  try {
                      secretBytes = MessageDigest.getInstance("md5").digest(
                              plainText.getBytes());
                  } catch (NoSuchAlgorithmException e) {
                      throw new RuntimeException("沒有md5這個(gè)算法!");
                  }
                  return new BigInteger(1, secretBytes).toString(16);// 將加密后byte數(shù)組轉(zhuǎn)換16進(jìn)制表示
              }
          
          }
        
      • 于是,在上文的LoginServlet中,當(dāng)用戶登陸時(shí),就應(yīng)該加密,所以存入cookie的就是加密后的密碼,于是在LoginServlet中要加入一行代碼:

          ...
          // 獲得用戶名和密碼
          String username = request.getParameter("username");
          String password = request.getParameter("password");
          // MD5 加密
          password = DigestUtils.md5(password);
          ...
          Cookie cookie = new Cookie("autologin", URLEncoder.encode(username, "utf-8") + "." + password);
        

11、案例五:使用Filter實(shí)現(xiàn)URL級(jí)別的權(quán)限認(rèn)證

  • 情景:在實(shí)際開發(fā)中我們經(jīng)常把一些執(zhí)行敏感操作的servlet映射到一些特殊目錄中,并用filter把這些特殊目錄保護(hù)起來(lái),限制只能擁有相應(yīng)訪問權(quán)限的用戶才能訪問這些目錄下的資源。從而在我們系統(tǒng)中實(shí)現(xiàn)一種URL級(jí)別的權(quán)限功能。

  • 要求:為使Filter具有通用性,F(xiàn)ilter保護(hù)的資源和相應(yīng)的訪問權(quán)限通過filter參數(shù)的形式予以配置。

  • 假設(shè):

      WebRoot/public/ 該目錄下的資源所有身份都可以訪問 : 用戶注冊(cè)、用戶登錄
      WebRoot/user/ 該目錄下的資源只有普通用戶/超級(jí)管理員可以訪問: 購(gòu)買商品,查看購(gòu)物車,訂單支付
      WebRoot/admin/ 該目錄下的資源只有超級(jí)管理員可以訪問:查看商品銷售情況,添加商品
    
  • 新建JSP,在index.jsp頁(yè)面提供八個(gè)鏈接,分別進(jìn)入上文說的八個(gè)功能界面中(用戶注冊(cè)...添加商品)

    • web.xml配置:

          <filter>
              <filter-name>PrivilegeFilter</filter-name>
              <filter-class>cn.itcast.filter.PrivilegeFilter</filter-class>
          </filter>
          <filter-mapping>
              <filter-name>PrivilegeFilter</filter-name>
              <url-pattern>/*</url-pattern>
          </filter-mapping>    
      
    • PrivilegeFilter:

        public class PrivilegeFilter implements Filter {
        
            @Override
            public void destroy() {
            }
        
            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                // 權(quán)限控制
                // 1、獲得當(dāng)前請(qǐng)求訪問資源路徑
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                String path = httpServletRequest.getRequestURI().substring(
                        httpServletRequest.getContextPath().length());
                System.out.println(path);
                // 2、如果路徑 以/public/ 開始 ----- 游客就可以訪問 無(wú)需登陸
                if (path.startsWith("/public/")) {
                    chain.doFilter(httpServletRequest, response);
                    return;
                } else {
                    // 需要 用戶身份 或者 管理員 --- 需要登陸 ----- 判斷是否登陸
                    User user = (User) httpServletRequest.getSession().getAttribute(
                            "user");
                    if (user == null) {
                        // 未登陸--- 沒有權(quán)限 --- 跳轉(zhuǎn)到登陸頁(yè)面
                        request.setAttribute("msg", "您還沒有登陸!");
                        request.getRequestDispatcher("/public/login.jsp").forward(
                                httpServletRequest, response);
                        return;
                    } else {
                        // 已經(jīng)登陸 --- 用戶有身份
                        if (path.startsWith("/user/")) { // user 身份
                            // 需要 用戶身份、管理員身份‘
                            if (user.getRole().equals("user")
                                    || user.getRole().equals("admin")) {
                                // 權(quán)限滿足
                                chain.doFilter(httpServletRequest, response);
                                return;
                            } else {
                                throw new RuntimeException("對(duì)不起您的權(quán)限不足!");
                            }
                        }
        
                        if (path.startsWith("/admin/")) { // 管理員身份
                            // 需要管理員身份
                            if (user.getRole().equals("admin")) {
                                // 權(quán)限滿足
                                chain.doFilter(httpServletRequest, response);
                                return;
                            } else {
                                throw new RuntimeException("對(duì)不起您的權(quán)限不足!");
                            }
                        }
                    }
                }
        
                chain.doFilter(httpServletRequest, response);
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
        
        }
      
    • index.jsp

        <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8"%>
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
        <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        </head>
        <body>
        <h1>商城主頁(yè)</h1> <h2>${(empty user)?"您還未登錄":user.username }</h2>
        <!-- 不登陸就可以訪問 -->
        <a href="/day17privilege/public/login.jsp">用戶登錄</a><br/>
        <a href="/day17privilege/public/regist.jsp">用戶注冊(cè)</a><br/>
        <!-- 需要具有商品用戶權(quán)限 -->
        <a href="/day17privilege/user/buyproduct.jsp">購(gòu)買商品</a><br/>
        <a href="/day17privilege/user/showcart.jsp">查看購(gòu)物車</a><br/>
        <a href="/day17privilege/user/pay.jsp">訂單支付</a><br/>
        <!-- 需要具有管理員權(quán)限 -->
        <a href="/day17privilege/admin/addproduct.jsp">添加商品</a><br/>
        <a href="/day17privilege/admin/showsaleinfo.jsp">查看商品銷售情況</a><br/>
        </body>
        </html>
      
  • 注意:login.jsp需要放在public文件夾下面,LoginServlet需要將URL映射到public文件夾下面。

Java Web 之 Servlet:http://www.itdecent.cn/p/60bad0a4a1af

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 本文包括:1、什么是自動(dòng)登錄?2、簡(jiǎn)書自動(dòng)登錄流程3、實(shí)現(xiàn)自動(dòng)登錄4、用戶注銷 1、什么是自動(dòng)登錄? 就拿簡(jiǎn)書做例...
    廖少少閱讀 6,200評(píng)論 1 27
  • 本文包括:1、Listener簡(jiǎn)介2、Servlet監(jiān)聽器3、監(jiān)聽三個(gè)域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器4、監(jiān)聽三個(gè)域?qū)?..
    廖少少閱讀 6,618評(píng)論 6 28
  • Qunar大講堂1.web項(xiàng)目結(jié)構(gòu)1.1 web項(xiàng)目結(jié)構(gòu)2.servlet2.1 servlet2.2 liste...
    拾壹北閱讀 1,093評(píng)論 2 2
  • 監(jiān)聽器(listener) 監(jiān)聽器簡(jiǎn)介 :監(jiān)聽器就是一個(gè)實(shí)現(xiàn)特定接口的普通java程序,這個(gè)程序?qū)iT用于監(jiān)聽另一個(gè)...
    奮斗的老王閱讀 2,671評(píng)論 0 53

友情鏈接更多精彩內(nèi)容