我在上海樂位元組學習的第二十一天(持續更新中)
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物件,壓縮響應正文內容。
相關文章
- 我在上海樂位元組學習java的第十一天(持續更新中)Java
- 在上海樂位元組學習Java的第十七天Java
- 在樂位元組學習的第三天
- AnimalController 學習 持續更新Controller
- PHP 的自動載入(持續學習更新中)PHP
- 【Elasticsearch學習】DSL搜尋大全(持續更新中)Elasticsearch
- drupal7學習筆記—–(持續更新中…)筆記
- 學習 Laravel —— 前端篇(持續更新)Laravel前端
- 【持續更新...】ligerGrid 學習筆記筆記
- 【持續更新...】ECharts學習筆記Echarts筆記
- 【持續更新...】Nginx 學習筆記Nginx筆記
- Java 學習筆記(持續更新)Java筆記
- 我的Android開發框架Collection(持續更新中)Android框架
- 鴻蒙OS學習資料整理,持續更新中鴻蒙
- 【持續更新...】Microsoft SSIS 學習筆記ROS筆記
- 2020年大資料學習大綱(持續更新中...)大資料
- 機器學習完整資源推薦(持續更新中)機器學習
- 前端學習資源彙總(持續更新)前端
- Kotlin學習資料彙總(持續更新...)Kotlin
- Git在專案中的那些實操(持續更新...)Git
- C語言初學習——易錯點合集(持續更新中)C語言
- 2021年最新整理, C++ 學習資料[持續更新中]C++
- 學習《Java虛擬機器》目錄索引(持續更新中)Java虛擬機索引
- git使用、持續更新中Git
- Node.js 學習筆記_20170924(持續更新…)Node.js筆記
- 如果你也打算學習 Spring Cloud [持續更新]SpringCloud
- (持續更新)Qt3D 學習資源QT3D
- 愛玩手機的貓Linux學習筆記(持續更新)Linux筆記
- Linux 系統化學習系列文章總目錄(持續更新中)Linux
- 關於在Flutter Web中載入html(持續更新中......)FlutterWebHTML
- Kotlin系列教程——史上最全面、最詳細的學習教程,持續更新中....Kotlin
- 數學小結(持續更新)
- [Android學習筆記]雜碎知識(持續更新)Android筆記
- PHP學習路線資源總結[持續更新]PHP
- 一個前端工程師的Docker學習筆記【持續更新】前端工程師Docker筆記
- javaScript 習題總結(持續更新)JavaScript
- 一些JavaSE學習過程中的思路整理(主觀性強,持續更新中...)Java
- Cadence物理庫 LEF 檔案語法學習【持續更新】