文檔版本 | 開發工具 | 測試平臺 | 工程名字 | 日期 | 作者 | 備註 |
---|---|---|---|---|---|---|
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資源不會被訪問。
- web服務器在調用doFilter方法時,會傳遞一個
- 調用目標資源之後,讓一段代碼執行
開發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**上下文對象**的引用。
- String
註冊與映射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
結束。
- 目錄匹配
- 1 . 完全匹配 必須以
<servlet-name>
指定過濾器所攔截的Servlet名稱。<dispatcher>
指定過濾器所攔截的資源被 Servlet 容器調用的方式,可以是REQUEST
,INCLUDE
,FORWARD
和ERROR
之一,默認REQUEST
。用戶可以設置多個<dispatcher>
子元素用來指定 Filter 對資源的多種調用方式進行攔截。<dispatcher>
子元素可以設置的值及其意義: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進行自動登錄操作
- 得到autologin這個cookie
Cookie cookie = CookieUtils.findCookieByName(request.getCookies(),"autologin");
- 得到username與password進行登錄操作
- 得到autologin這個cookie
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增強案例—壓縮響應
- 應用HttpServletResponseWrapper對象,壓縮響應正文內容。
- 思路:
- 通過filter向目標頁面傳遞一個自定義的response對象。
- 在自定義的response對象中,重寫getOutputStream方法和getWriter方法,使目標資源調用此方法輸出頁面內容時,獲得的是我們自定義的ServletOutputStream對象。
- 在我們自定義的ServletOuputStream對象中,重寫write方法,使寫出的數據寫出到一個buffer中。
- 當頁面完成輸出後,在filter中就可得到頁面寫出的數據,從而我們可以調用GzipOuputStream對數據進行壓縮後再寫出給瀏覽器,以此完成響應正文件壓縮功能。
- 通過filter向目標頁面傳遞一個自定義的response對象。