本文包括:
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è)步驟:
調(diào)用目標(biāo)資源之前,讓一段代碼執(zhí)行
-
是否調(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); }
-
調(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é)
對(duì)一個(gè)web資源可以配置多個(gè)過濾器
一個(gè)過濾器可以用來(lái)過濾多個(gè)web 資源
-
在
<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>
-
關(guān)于
<url-pattern>的寫法Filter和Servlet相同,有三種方式,完全匹配、目錄匹配、擴(kuò)展名匹配,詳解見:Java Web 之 Servlet-
在
<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)求的亂碼解決方式有兩種:
配置tomcat server.xml中的URIEncoding="utf-8"
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-cachecache-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)登陸案例問題:
-
如果用戶名中文怎么辦?
解決方案:保存cookie時(shí)進(jìn)行手動(dòng)編碼 URL編碼
生成cookie時(shí),new Cookie(URLEncoder.encode(用戶名,"utf-8")); ------ 寫在LoginServlet
讀取cookie時(shí),URLDecoder.decode(用戶名,"utf-8"); ---- 寫在AutoLoginFilter
-
中文名和密碼安全問題
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


