正確優雅的解決用戶退出問題——JSP和Struts解決方案
本文是由
馬嘉楠
翻譯的javaworld.com上的一篇名為《Solving the logout problem properly and elegantly》的文章,原文請參看
Solving the logout problem properly and elegantly
。
花了2天翻譯過后才發現wolfmanchen已經捷足先登了,而且翻譯得很準確(比我的好,^+^)我進行了修改,在此謝謝wolfmanchen。
文中所有示例程序的代碼可以從javaworld.com中下載,文章后面有資源鏈接。
我看過之后覺得很好,希望對你也有所幫助!
???????????????????????????? ?正確優雅的解決用戶退出問題
?????????????????????????????????????????? ------JSP和Struts解決方案
摘要
在一個有密碼保護的Web應用當中,正確妥善的處理用戶退出過程并不僅僅只需要調用HttpSession對象的invalidate()方法,因為現在大部分瀏覽器上都有后退(Back)和前進(Forward)按鈕,允許用戶后退或前進到一個頁面。在用戶退出一個Web應用之后,如果按了后退按鈕,瀏覽器把緩存中的頁面呈現給用戶,這會使用戶產生疑惑,他們會開始擔心他們的個人數據是否安全。
實際上,許多Web應用會彈出一個頁面,警告用戶退出時關閉整個瀏覽器,以此來阻止用戶點擊后退按鈕。還有一些使用JavaScript,但在某些客戶端瀏覽器中這卻不一定起作用。這些解決方案大多數實現都很笨拙,且不能保證在任何情況下都100%有效,同時,它還要求用戶有一定的操作經驗。
這篇文章以簡單的程序示例闡述了正確解決用戶退出問題的方案。作者Kevin Le首先描述了一個理想的密碼保護Web應用,然后以示例程序解釋問題如何產生并討論解決問題的方案。文章雖然是針對JSP進行討論闡述,但作者所闡述的概念很容易理解而且能夠為其他Web技術所采用。最后最后,作者Kevin Le用Jakarta Struts更為優雅地解決用戶退出問題。文中包含JSP和Struts的示例程序 (3,700 words; September 27, 2004)
大部分Web應用不會包含像銀行賬戶或信用卡資料那樣機密的信息,但是一旦涉及到敏感數據,就需要我們提供某些密碼保護機制。例如,在一個工廠當中,工人必須通過Web應用程序訪問他們的時間安排、進入他們的培訓課程以及查看他們的薪金等等。此時應用SSL(Secure Socket Layer)就有些大材小用了(SSL頁面不會在緩存中保存,關于SSL的討論已經超出本文的范圍)。但是這些應用又確實需要某種密碼保護措施,否則,工人(在這種情況下,也就是Web應用的使用者)就可以發現工廠中所有員工的私人機密信息。
類似上面的情況還包括位于公共圖書館、醫院、網吧等公共場所的計算機。在這些地方,許多用戶共同使用幾臺計算機,此時保護用戶的個人數據就顯得至關重要。 同時應用程序的良好設計與實現對用戶專業知識以及相關培訓要求少之又少。
讓我們來看一下現實世界中一個完美的Web應用是怎樣工作的:
1. 用戶在瀏覽器中輸入URL,訪問一個頁面。
2. Web應用顯示一個登陸頁面,要求用戶輸入有效的驗證信息。
3. 用戶輸入用戶名和密碼。
4. 假設用戶提供的驗證信息是正確的,經過了驗證過程,Web應用允許用戶瀏覽他有權訪問的區域。
5. 退出時,用戶點擊頁面的退出按鈕,Web應用顯示確認頁面,詢問用戶是否真的需要退出。一旦用戶點擊確定按鈕,Session結束,Web應用重新定位到登陸頁面。用戶現在可以放心的離開而不用擔心他的信息會被泄露。
6. 另一個用戶坐到了同一臺電腦前。他點擊后退按鈕,Web應用不應該顯示上一個用戶訪問過的任何一個頁面。
事實上,Web應用將一直停留在登陸頁面上,除非第二個用戶提供正確的驗證信息,之后才可以訪問他有權限的區域。
通過示例程序,文章向您闡述了如何在一個Web應用中實現上面的功能。
一. JSP samples
為了更為有效地向您說明這個解決方案,本文將從展示一個Web應用logoutSampleJSP1中碰到的問題開始。這個示例代表了許多沒有正確解決退出過程的Web應用。logoutSampleJSP1包含一下JSP頁面:login.jsp,? home.jsp,? secure1.jsp,? secure2.jsp,? logout.jsp,? loginAction.jsp,?和 logoutAction.jsp。其中頁面home.jsp,? secure1.jsp,? secure2.jsp, 和 logout.jsp是不允許未經認證的用戶訪問的,也就是說,這些頁面包含了重要信息,在用戶登陸之前或者退出之后都不應該顯示在瀏覽器中。login.jsp頁面包含了用于用戶輸入用戶名和密碼的form。logout.jsp頁面包含了要求用戶確認是否退出的form。loginAction.jsp和logoutAction.jsp作為控制器分別包含了登陸和退出動作的代碼。
第二個Web示例應用logoutSampleJSP2展示了如何糾正示例logoutSampleJSP1中的問題。但是第二個示例logoutSampleJSP2自身也是有問題的。在特定情況下,退出問題依然存在。
第三個Web示例應用logoutSampleJSP3對logoutSampleJSP2進行了改進,比較妥善地解決了退出問題。
最后一個Web示例logoutSampleStruts展示了JakartaStruts如何優雅地解決退出問題。
注意:本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant瀏覽器上測試通過。
二. Login action
Brian Pontarelli的經典文章 《J2EE Security: Container Versus Custom》 討論了不同的J2EE認證方法。文章同時指出,HTTP協議和基于form的認證方法并不能提供處理用戶退出問題的機制。因此,解決方法便是引入用戶自定義的安全實現機制,這就提供了更大的靈活性。
在用戶自定義的認證方法中,普遍采用的方法是從用戶提交的form中獲得用戶輸入的認證信息,然后到諸如LDAP (lightweight directory access protocol)或關系數據庫(relational database management system, RDBMS)的安全域中進行認證。如果用戶提供的認證信息是有效的,登陸動作在HttpSession對象中保存某個對象。HttpSession存在著保存的對象則表示用戶已經登陸到Web應用當中。為了方便起見,本文所附的示例只在HttpSession中保存一個用戶名以表明用戶已經登陸。清單1是從loginAction.jsp頁面中節選的一段代碼以此講解登陸動作:
Listing 1
//...
//initialize RequestDispatcher object; set forward to home page by default
RequestDispatcher rd = request.getRequestDispatcher( "home.jsp" );
//Prepare connection and statement
rs = stmt.executeQuery( "select password from USER where userName = '" + userName + "'" );
if (rs.next()) {
//Query only returns 1 record in the result set;
//Only 1 password per userName which is also the primary key
????if (rs.getString( "password" ).equals(password)) { //If valid password
??????? session.setAttribute( "User" , userName); //Saves username string in the session object
??? }
??? else { //Password does not match, i.e., invalid user password
?????? ?request.setAttribute( "Error" , "Invalid password." );
?????? ?rd = request.getRequestDispatcher( "login.jsp" );
??? }
} //No record in the result set, i.e., invalid username
??? else {
??????? request.setAttribute( "Error" , "Invalid user name." );
??????? rd = request.getRequestDispatcher( "login.jsp" );
??? }
}
//As a controller, loginAction.jsp finally either forwards to "login.jsp" or "home.jsp"
rd.forward(request, response);
//...
花了2天翻譯過后才發現wolfmanchen已經捷足先登了,而且翻譯得很準確(比我的好,^+^)我進行了修改,在此謝謝wolfmanchen。
文中所有示例程序的代碼可以從javaworld.com中下載,文章后面有資源鏈接。
我看過之后覺得很好,希望對你也有所幫助!
???????????????????????????? ?正確優雅的解決用戶退出問題
?????????????????????????????????????????? ------JSP和Struts解決方案
摘要
在一個有密碼保護的Web應用當中,正確妥善的處理用戶退出過程并不僅僅只需要調用HttpSession對象的invalidate()方法,因為現在大部分瀏覽器上都有后退(Back)和前進(Forward)按鈕,允許用戶后退或前進到一個頁面。在用戶退出一個Web應用之后,如果按了后退按鈕,瀏覽器把緩存中的頁面呈現給用戶,這會使用戶產生疑惑,他們會開始擔心他們的個人數據是否安全。
實際上,許多Web應用會彈出一個頁面,警告用戶退出時關閉整個瀏覽器,以此來阻止用戶點擊后退按鈕。還有一些使用JavaScript,但在某些客戶端瀏覽器中這卻不一定起作用。這些解決方案大多數實現都很笨拙,且不能保證在任何情況下都100%有效,同時,它還要求用戶有一定的操作經驗。
這篇文章以簡單的程序示例闡述了正確解決用戶退出問題的方案。作者Kevin Le首先描述了一個理想的密碼保護Web應用,然后以示例程序解釋問題如何產生并討論解決問題的方案。文章雖然是針對JSP進行討論闡述,但作者所闡述的概念很容易理解而且能夠為其他Web技術所采用。最后最后,作者Kevin Le用Jakarta Struts更為優雅地解決用戶退出問題。文中包含JSP和Struts的示例程序 (3,700 words; September 27, 2004)
大部分Web應用不會包含像銀行賬戶或信用卡資料那樣機密的信息,但是一旦涉及到敏感數據,就需要我們提供某些密碼保護機制。例如,在一個工廠當中,工人必須通過Web應用程序訪問他們的時間安排、進入他們的培訓課程以及查看他們的薪金等等。此時應用SSL(Secure Socket Layer)就有些大材小用了(SSL頁面不會在緩存中保存,關于SSL的討論已經超出本文的范圍)。但是這些應用又確實需要某種密碼保護措施,否則,工人(在這種情況下,也就是Web應用的使用者)就可以發現工廠中所有員工的私人機密信息。
類似上面的情況還包括位于公共圖書館、醫院、網吧等公共場所的計算機。在這些地方,許多用戶共同使用幾臺計算機,此時保護用戶的個人數據就顯得至關重要。 同時應用程序的良好設計與實現對用戶專業知識以及相關培訓要求少之又少。
讓我們來看一下現實世界中一個完美的Web應用是怎樣工作的:
1. 用戶在瀏覽器中輸入URL,訪問一個頁面。
2. Web應用顯示一個登陸頁面,要求用戶輸入有效的驗證信息。
3. 用戶輸入用戶名和密碼。
4. 假設用戶提供的驗證信息是正確的,經過了驗證過程,Web應用允許用戶瀏覽他有權訪問的區域。
5. 退出時,用戶點擊頁面的退出按鈕,Web應用顯示確認頁面,詢問用戶是否真的需要退出。一旦用戶點擊確定按鈕,Session結束,Web應用重新定位到登陸頁面。用戶現在可以放心的離開而不用擔心他的信息會被泄露。
6. 另一個用戶坐到了同一臺電腦前。他點擊后退按鈕,Web應用不應該顯示上一個用戶訪問過的任何一個頁面。
事實上,Web應用將一直停留在登陸頁面上,除非第二個用戶提供正確的驗證信息,之后才可以訪問他有權限的區域。
通過示例程序,文章向您闡述了如何在一個Web應用中實現上面的功能。
一. JSP samples
為了更為有效地向您說明這個解決方案,本文將從展示一個Web應用logoutSampleJSP1中碰到的問題開始。這個示例代表了許多沒有正確解決退出過程的Web應用。logoutSampleJSP1包含一下JSP頁面:login.jsp,? home.jsp,? secure1.jsp,? secure2.jsp,? logout.jsp,? loginAction.jsp,?和 logoutAction.jsp。其中頁面home.jsp,? secure1.jsp,? secure2.jsp, 和 logout.jsp是不允許未經認證的用戶訪問的,也就是說,這些頁面包含了重要信息,在用戶登陸之前或者退出之后都不應該顯示在瀏覽器中。login.jsp頁面包含了用于用戶輸入用戶名和密碼的form。logout.jsp頁面包含了要求用戶確認是否退出的form。loginAction.jsp和logoutAction.jsp作為控制器分別包含了登陸和退出動作的代碼。
第二個Web示例應用logoutSampleJSP2展示了如何糾正示例logoutSampleJSP1中的問題。但是第二個示例logoutSampleJSP2自身也是有問題的。在特定情況下,退出問題依然存在。
第三個Web示例應用logoutSampleJSP3對logoutSampleJSP2進行了改進,比較妥善地解決了退出問題。
最后一個Web示例logoutSampleStruts展示了JakartaStruts如何優雅地解決退出問題。
注意:本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant瀏覽器上測試通過。
二. Login action
Brian Pontarelli的經典文章 《J2EE Security: Container Versus Custom》 討論了不同的J2EE認證方法。文章同時指出,HTTP協議和基于form的認證方法并不能提供處理用戶退出問題的機制。因此,解決方法便是引入用戶自定義的安全實現機制,這就提供了更大的靈活性。
在用戶自定義的認證方法中,普遍采用的方法是從用戶提交的form中獲得用戶輸入的認證信息,然后到諸如LDAP (lightweight directory access protocol)或關系數據庫(relational database management system, RDBMS)的安全域中進行認證。如果用戶提供的認證信息是有效的,登陸動作在HttpSession對象中保存某個對象。HttpSession存在著保存的對象則表示用戶已經登陸到Web應用當中。為了方便起見,本文所附的示例只在HttpSession中保存一個用戶名以表明用戶已經登陸。清單1是從loginAction.jsp頁面中節選的一段代碼以此講解登陸動作:
Listing 1
//...
//initialize RequestDispatcher object; set forward to home page by default
RequestDispatcher rd = request.getRequestDispatcher( "home.jsp" );
//Prepare connection and statement
rs = stmt.executeQuery( "select password from USER where userName = '" + userName + "'" );
if (rs.next()) {
//Query only returns 1 record in the result set;
//Only 1 password per userName which is also the primary key
????if (rs.getString( "password" ).equals(password)) { //If valid password
??????? session.setAttribute( "User" , userName); //Saves username string in the session object
??? }
??? else { //Password does not match, i.e., invalid user password
?????? ?request.setAttribute( "Error" , "Invalid password." );
?????? ?rd = request.getRequestDispatcher( "login.jsp" );
??? }
} //No record in the result set, i.e., invalid username
??? else {
??????? request.setAttribute( "Error" , "Invalid user name." );
??????? rd = request.getRequestDispatcher( "login.jsp" );
??? }
}
//As a controller, loginAction.jsp finally either forwards to "login.jsp" or "home.jsp"
rd.forward(request, response);
//...
本文當中所附Web應用示例均以關系型數據庫作為安全域,但本問所講述的內容同樣適用于其他任何類型的安全域。
三. Logout action
退出動作包含刪除用戶名以及調用用戶的HttpSession對象的invalidate()方法。清單2是從loginoutAction.jsp中節選的一段代碼,以此說明退出動作:
Listing 2
//...
session.removeAttribute( "User" );
session.invalidate();
//...
四. 阻止未經認證訪問受保護的JSP頁面
從提交的form中獲取用戶提交的認證信息并經過驗證后,登陸動作僅僅在HttpSession對象中寫入一個用戶名。退出動作則剛好相反,它從HttpSession中刪除用戶名并調用HttpSession對象的invalidate()方法。為了使登陸和退出動作真正發揮作用,所有受保護的JSP頁面必須首先驗證HttpSession中包含的用戶名,以便確認用戶當前是否已經登陸。如果HttpSession中包含了用戶名,就說明用戶已經登陸,Web應用會將剩余的JSP頁中的動態內容發送給瀏覽器。否則,JSP頁將跳轉到登陸頁面,login.jsp。頁面home.jsp,? secure1.jsp,? secure2.jsp和 logout.jsp均包含清單3中的代碼段:
Listing 3
//...
String userName = (String) session.getAttribute( "User" );
if (null == userName) {
??? request.setAttribute( "Error" , "Session has ended. Please login." );
??? RequestDispatcher rd = request.getRequestDispatcher( "login.jsp" );
??? rd.forward(request, response);
}
//...
//Allow the rest of the dynamic content in this JSP to be served to the browser
//...
在這個代碼段中,程序從HttpSession中檢索username字符串。如果username字符串為空,Web應用則自動中止執行當前頁面并跳轉到登陸頁,同時給出錯誤信息“Session has ended. Please log in.”;如果不為空,Web應用繼續執行,把剩余的頁面提供給用戶,從而使JSP頁面的動態內容成為服務對象。
五.運行logoutSampleJSP1
運行logoutSampleJSP1將會出現如下幾種情形:
? 如果用戶沒有登陸,Web應用將會正確中止受保護頁面home.jsp,? secure1.jsp,? secure2.jsp和logout.jsp中動態內容的執行。也就是說,假如用戶并沒有登陸,但是在瀏覽器地址欄中直接敲入受保護JSP頁的地址試圖訪問,Web應用將自動跳轉到登陸頁面,同時顯示錯誤信息“Session has ended.Please log in.”
? 同樣的,當一個用戶已經退出,Web應用將會正確中止受保護頁面home.jsp,? secure1.jsp,? secure2.jsp和logout.jsp中動態內容的執行。也就是說,用戶退出以后,如果在瀏覽器地址欄中直接敲入受保護JSP頁的地址試圖訪問,Web應用將自動跳轉到登陸頁面,同時顯示錯誤信息“Session has ended.Please log in.”
? 用戶退出以后,如果點擊瀏覽器上的后退按鈕返回到先前的頁面,Web應用將不能正確保護受保護的JSP頁面——在Session銷毀后(用戶退出)受保護的JSP頁會重新顯示在瀏覽器中。然而,點擊該頁面上的任何鏈接,Web應用都會跳轉到登陸頁面,同時顯示錯誤信息“Session has ended.Please log in.”
六. 阻止瀏覽器緩存
?
上述問題的根源就在于現代大部分瀏覽器都有一個后退按鈕。當點擊后退按鈕時,默認情況下瀏覽器不會從Web服務器上重新獲取頁面,而是簡單的從瀏覽器緩存中重新載入頁面。這個問題并不僅限于基于Java(JSP/servlets/Struts) 的Web應用當中,在基于PHP (Hypertext Preprocessor)、ASP、(Active Server Pages)、和.NET的Web應用中也同樣存在。
在用戶點擊后退按鈕之后,瀏覽器到Web服務器(一般來說)或者應用服務器(在java的情況下)再從服務器到瀏覽器這樣通常意義上的HTTP回路并沒有建立。僅僅只是用戶,瀏覽器和緩存之間進行了交互。所以即使受保護的JSP頁面,例如home.jsp,? secure1.jsp,? secure2.jsp和logout.jsp包含了清單3上的代碼,當點擊后退按鈕時,這些代碼也永遠不會執行的。
緩存的好壞,真是仁者見仁智者見智。緩存事實上的確提供了一些便利,但這些便利通常只存在于靜態的HTML頁面或基于圖形或影像的頁面。而另一方面,Web應用通常是面向數據的。由于Web應用中的數據頻繁變更,所以與為了節省時間從緩存中讀取并顯示過期的數據相比,提供最新的數據顯得尤為重要!
幸運的是,HTTP頭信息“Expires”和“Cache-Control”為應用程序服務器提供了一個控制瀏覽器和代理服務器上緩存的機制。HTTP頭信息Expires告訴代理服務器它的緩存頁面何時將過期。HTTP1.1規范中新定義的頭信息Cache-Control在Web應用當中可以通知瀏覽器不緩存任何頁面。當點擊后退按鈕時,瀏覽器發送Http請求道應用服務器以便獲取該頁面的最新拷貝。如下是使用Cache-Control的基本方法:
? no-cache:強制緩存從服務器上獲取該頁面的最新拷貝
? no-store: 在任何情況下緩存不保存該頁面
HTTP1.0規范中的Pragma:no-cache等同于HTTP1.1規范中的Cache-Control:no-cache,同樣可以包含在頭信息中。
通過使用HTTP頭信息的cache控制,第二個示例應用logoutSampleJSP2解決了logoutSampleJSP1的問題。logoutSampleJSP2與logoutSampleJSP1不同表現在如下代碼段中,這一代碼段加入進所有受保護的頁面中:
//...
response.setHeader( "Cache-Control" , "no-cache" ); //Forces caches to obtain a new copy of the page from the origin server
response.setHeader( "Cache-Control" , "no-store" ); //Directs caches not to store the page under any circumstance
response.setDateHeader( "Expires" , 0); //Causes the proxy cache to see the page as "stale"
response.setHeader( "Pragma" , "no-cache" ); //HTTP 1.0 backward compatibility
String userName = (String) session.getAttribute( "User" );
if (null == userName) {
??? request.setAttribute( "Error" , "Session has ended. Please login." );
??? RequestDispatcher rd = request.getRequestDispatcher( "login.jsp" );
??? rd.forward(request, response);
}
//...
通過設置頭信息和檢查HttpSession對象中的用戶名來確保瀏覽器不會緩存JSP頁面。同時,如果用戶未登陸,JSP頁面的動態內容不會發送到瀏覽器,取而代之的將是登陸頁面login.jsp。
七. 運行logoutSampleJSP2
運行Web示例應用logoutSampleJSP2后將會看到如下結果:
? 當用戶退出后試圖點擊后退按鈕,瀏覽器不會重新顯示受保護的頁面,它只會顯示登陸頁login.jsp同時給出提示信息Session has ended. Please log in.
? 然而,當按了后退按鈕返回的頁是處理用戶提交數據的頁面時,IE和Avant瀏覽器將彈出如下信息提示:
?????????? 警告:頁面已過期
?????????? The page you requested was created using information you submitted in a form.?This page is?no longer available. As a security precaution, Internet Explorer does not automatically? resubmit your information for you.
Mozilla和FireFox瀏覽器將會顯示一個對話框,提示信息如下:
??????????? The page you are trying to view contains POSTDATA that has expired from cache. If you? resend the data, any action from the form carried out (such as a search or online purchase) will be repeated. To resend the data, click OK. Otherwise, click Cancel.
在IE和Avant瀏覽器中選擇刷新或者在Mozilla和FireFox瀏覽器中選擇重新發送數據后,前一個JSP頁面將重新顯示在瀏覽器中。顯然的,這病不是我們所想看到的因為它違背了logout動作的目的。發生這一現象時,很可能是一個惡意用戶在嘗試獲取其他用戶的數據。然而,這個問題僅僅出現在點擊后退按鈕后,瀏覽器返回到一個處理POST請求的頁面。
八. 記錄最后登陸時間
上述問題的發生是因為瀏覽器重新提交了其緩存中的數據。這本文的例子中,數據包含了用戶名和密碼。盡管IE瀏覽器給出了安全警告信息,但事實上瀏覽器此時起到了負面作用。
為了解決logoutSampleJSP2中出現的問題,logoutSampleJSP3的login.jsp除了包含username和password的之外,還增加了一個稱作lastLogon的隱藏表單域,此表單域將會動態的被初始化為一個long型值。這個long型值是通過調用System.currentTimeMillis()獲取到的自1970年1月1日以來的毫秒數。當login.jsp中的form提交時,loginAction.jsp首先將隱藏域中的值與用戶數據庫中的lastLogon值進行比較。只有當lastLogon表單域中的值大于數據庫中的值時Web應用才認為這是個有效的登陸。
為了驗證登陸,數據庫中lastLogon字段必須用表單中的lastLogon值進行更新。上例中,當瀏覽器重復提交緩存中的數據時,表單中的lastLogon值不比數據庫中的lastLogon值大,因此,loginAction將跳轉到login.jsp頁面,并顯示如下錯誤信息“Session has ended.Please log in.”清單5是loginAction中節選的代碼段:
清單5
//...
RequestDispatcher rd = request.getRequestDispatcher( "home.jsp" ); //Forward to homepage by default
//...
if (rs.getString( "password" ).equals(password)) { //If valid password
??? long lastLogonDB = rs.getLong( "lastLogon" );
??? if (lastLogonForm > lastLogonDB) {
??????? session.setAttribute( "User" , userName); //Saves username string in the session object
??????? stmt.executeUpdate( "update USER set lastLogon= " + lastLogonForm + " where userName = '" + userName + "'" );
??? }
??? else {
??????? request.setAttribute( "Error" , "Session has ended. Please login." );
??????? rd = request.getRequestDispatcher( "login.jsp" ); }
??? }
else { //Password does not match, i.e., invalid user password
??? request.setAttribute( "Error" , "Invalid password." );
??? rd = request.getRequestDispatcher( "login.jsp" );
}
//...
rd.forward(request, response);
//...
為了實現上述方法,你必須記錄每個用戶的最后登陸時間。對于采用關系型數據庫安全域來說,這點可以可以通過在某個表中加上lastLogin字段輕松實現。雖然對LDAP以及其他的安全域來說需要稍微動下腦筋,但最后登陸方法很顯然是可以實現的。
表示最后登陸時間的方法有很多。示例logoutSampleJSP3利用了自1970年1月1日以來的毫秒數。這個方法即使在許多人在不同瀏覽器中用一個用戶賬號登陸時也是可行的。
九. 運行logoutSampleJSP3
運行示例logoutSampleJSP3將展示如何正確處理退出問題。一旦用戶退出,點擊瀏覽器上的后退按鈕在任何情況下都不會在瀏覽器中顯示受保護的JSP頁面。這個示例展示了如何正確處理退出問題而不需要對用戶進行額外的培訓。
為了使代碼更簡練有效,一些冗余的代碼可以剔除。一種途徑就是把清單4中的代碼寫到一個單獨的JSP頁中,其他JSP頁面可以通過標簽 <jsp:include>進行使用
十. Struts框架下的退出實現
與直接使用JSP或JSP/servlets進行Web應用開發相比,另一個更好的可選方案是使用Struts。對于一個基于Struts的Web應用來說,添加一個處理退出問題的框架可以優雅地不費氣力的實現。這歸功于Struts是采用MVC設計模式的,因此可以將模型和視圖代碼清晰的分離。另外,Java是一個面向對象的語言,支持繼承,可以比JSP中的腳本更為容易地實現代碼重用。對于Struts來說,清單4中的代碼可以從JSP頁面中移植到Action類的execute()方法中。
此外,我們還可以定義一個繼承Struts Action類的Action基類,其execute()方法中包含了類似清單4中的代碼。通過繼承,其他Action類可以繼承基本類中的通用邏輯來設置HTTP頭信息以及檢索HttpSession對象中的username字符串。這個Action基類是一個抽象類并定義了一個抽象方法executeAction()。所有繼承自Action基類的子類都必須實現exectuteAction()方法而不是覆蓋它。通過繼承這一機制,所有繼承自Action基類的子類都不必再擔心退出代碼接口。(plumbing實在不知道怎么翻譯了,^+^,高手幫幫忙啊!原文:With this inheritance hierarchy in place, all of the base
Action
's subclasses no longer need to worry about any plumbing logout code.)。他們將只包含正常的業務邏輯代碼。清單6是基類的部分代碼:清單6
publicabstractclass BaseAction extends Action {
??? public ActionForward execute(ActionMapping mapping, ActionForm form,
???????????????????????????????????????????????? HttpServletRequest request, HttpServletResponse response)?
???????????????throws IOException, ServletException {
response.setHeader(
response.setHeader( "Cache-Control" , "no-store" ); //Directs caches not to store the page under any circumstance
response.setDateHeader( "Expires" , 0); //Causes the proxy cache to see the page as "stale"
response.setHeader( "Pragma" , "no-cache" ); //HTTP 1.0 backward compatibility
if (!this.userIsLoggedIn(request)) {
??? ActionErrors errors = new ActionErrors();
??? errors.add( "error" , new ActionError( "logon.sessionEnded" ));
??? this.saveErrors(request, errors);
??? return mapping.findForward( "sessionEnded" );
}
return executeAction(mapping, form, request, response);
}
protectedabstract ActionForward executeAction(ActionMapping mapping,? ActionForm?form,?HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException;
privateboolean userIsLoggedIn(HttpServletRequest request) {
if (request.getSession().getAttribute( "User" ) == null) {
??? return false;
}
return true;
}
}
清單6中的代碼與清單4中的很相像,唯一區別是用ActionMapping findForward替代了RequestDispatcher forward。清單6中,如果在HttpSession中未找到username字符串,ActionMapping對象將找到名為sessionEnded的forward元素并跳轉到對應的path。如果找到了,子類通過實現executeAction()方法,將執行他們自己的業務邏輯。因此,在struts-web.xml配置文件中為所有繼承自Action基類的子類聲明個一名為sessionEnded的forward元素并將其指向login.jsp是至關重要的。清單7以secure1 action闡明了這樣一個聲明:
清單7
<action path= "/secure1"
type= "com.kevinhle.logoutSampleStruts.Secure1Action"
scope= "request" >
<forward name= "success" path= "/WEB-INF/jsps/secure1.jsp" />
<forward name= "sessionEnded" path= "/login.jsp" />
</action>
繼承自BaseAction類的子類Secure1Action實現了executeAction()方法而不是覆蓋它。Secure1Action類不需要執行任何退出代碼,如清單8:
清單8
publicclass Secure1Action extends BaseAction {
??? public ActionForward executeAction(ActionMapping mapping, ActionForm form,
?????????????????????????????????????????????????????????? HttpServletRequest request, HttpServletResponse response)
?????????????? throws IOException, ServletException {
?????????????? HttpSession session = request.getSession();?
???????????????return (mapping.findForward( "success" ));
?????????}
}
上面的解決方案是如此的優雅有效,它僅僅只需要定義一個基類而不需要額外的代碼工作。將通用的行為方法寫成一個繼承StrutsAction的基類是者的推薦的,而且這是許多Struts項目的共同經驗。
十一. 局限性
上述解決方案對JSP或基于Struts的Web應用都是非常簡單而實用的,但它還是有某些局限。在我看來,這些局限并不是至關緊要的。
??? 通過取消與瀏覽器后退按鈕有關的緩存機制,一旦用戶離開頁面而沒有對數據進行提交,那么頁面將會丟失所有輸入的數據。即使點擊瀏覽器的后退按鈕返回到剛才的頁面也無濟于事,因為瀏覽器會從服務器獲取新的空白頁面顯示出來。一種可能的方法并不是阻止這些JSP頁面包含數據數據表格。在基于JSP的解決方案當中,那些JSP頁面可以刪除在清單4中的代碼。在基于Struts的解決方案當中,Action類需要繼承自Struts的Action類而非BaseAction類。
???上面講述的方法在Opera瀏覽器中不能工作。事實上沒有適用于Opera瀏覽器的解決方案,因為Opera瀏覽器與2616 Hypertext Transfer Protocol—HTTP/1.1緊密相關。Section 13.13 of?RFC 2616 states:??????????
User agents often have history mechanisms, such as "Back" buttons and history lists, which can be used to redisplay an entity retrieved earlier in a session.幸運的是,使用微軟的IE和基于Mozilla的瀏覽器用戶多余Opera瀏覽器。上面講述的解決方案對大多數用戶來說還是有幫助的。另外,無論是否使用上述的解決方案,Opera瀏覽器仍然存在用戶退出問題,就Opera來說沒有任何改變。然而,正如RFC2616中所說,通過像上面一樣設置頭文件指令,當用戶點擊一個鏈接時,Opera瀏覽器不會從緩存中獲取頁面。History mechanisms and caches are different. In particular history mechanisms SHOULD NOT try to show a semantically transparent view of the current state of a resource. Rather, a history mechanism is meant to show exactly what the user saw at the time when the resource was retrieved.
十二. 結論
這篇文章講述了處理退出問題的解決方案,盡管方案簡單的令人驚訝,但在所有情況下都能有效地工作。無論是對JSP還是Struts,所要做的不過是寫一段不超過50行的代碼以及一個記錄用戶最后登陸時間的方法。在有密碼保護的Web應用中使用這些方案能夠確保在任何情況下用戶的私人數據不致泄露,同時,也能增加用戶的經驗。
posted on 2006-08-25 08:55 liaojiyong 閱讀(321) 評論(0) 編輯 收藏