<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 1,  comments - 1,  trackbacks - 0
    來(lái)自:http://www.blogjava.cn/
       正確優(yōu)雅的解決用戶(hù)退出問(wèn)題
                                               ------JSP和Struts解決方案


    摘要

    在一個(gè)有密碼保護(hù)的Web應(yīng)用當(dāng)中,正確妥善的處理用戶(hù)退出過(guò)程并不僅僅只需要調(diào)用HttpSession對(duì)象的invalidate()方法,因?yàn)楝F(xiàn)在大部分瀏覽器上都有后退(Back)和前進(jìn)(Forward)按鈕,允許用戶(hù)后退或前進(jìn)到一個(gè)頁(yè)面。在用戶(hù)退出一個(gè)Web應(yīng)用之后,如果按了后退按鈕,瀏覽器把緩存中的頁(yè)面呈現(xiàn)給用戶(hù),這會(huì)使用戶(hù)產(chǎn)生疑惑,他們會(huì)開(kāi)始擔(dān)心他們的個(gè)人數(shù)據(jù)是否安全。

    實(shí)際上,許多Web應(yīng)用會(huì)彈出一個(gè)頁(yè)面,警告用戶(hù)退出時(shí)關(guān)閉整個(gè)瀏覽器,以此來(lái)阻止用戶(hù)點(diǎn)擊后退按鈕。還有一些使用JavaScript,但在某些客戶(hù)端瀏覽器中這卻不一定起作用。這些解決方案大多數(shù)實(shí)現(xiàn)都很笨拙,且不能保證在任何情況下都100%有效,同時(shí),它還要求用戶(hù)有一定的操作經(jīng)驗(yàn)。

    這篇文章以簡(jiǎn)單的程序示例闡述了正確解決用戶(hù)退出問(wèn)題的方案。作者Kevin Le首先描述了一個(gè)理想的密碼保護(hù)Web應(yīng)用,然后以示例程序解釋問(wèn)題如何產(chǎn)生并討論解決問(wèn)題的方案。文章雖然是針對(duì)JSP進(jìn)行討論闡述,但作者所闡述的概念很容易理解而且能夠?yàn)槠渌鸚eb技術(shù)所采用。最后最后,作者Kevin Le用Jakarta Struts更為優(yōu)雅地解決用戶(hù)退出問(wèn)題。文中包含JSP和Struts的示例程序 (3,700 words; September 27, 2004)




    大部分Web應(yīng)用不會(huì)包含像銀行賬戶(hù)或信用卡資料那樣機(jī)密的信息,但是一旦涉及到敏感數(shù)據(jù),就需要我們提供某些密碼保護(hù)機(jī)制。例如,在一個(gè)工廠當(dāng)中,工人必須通過(guò)Web應(yīng)用程序訪問(wèn)他們的時(shí)間安排、進(jìn)入他們的培訓(xùn)課程以及查看他們的薪金等等。此時(shí)應(yīng)用SSL(Secure Socket Layer)就有些大材小用了(SSL頁(yè)面不會(huì)在緩存中保存,關(guān)于SSL的討論已經(jīng)超出本文的范圍)。但是這些應(yīng)用又確實(shí)需要某種密碼保護(hù)措施,否則,工人(在這種情況下,也就是Web應(yīng)用的使用者)就可以發(fā)現(xiàn)工廠中所有員工的私人機(jī)密信息。

    類(lèi)似上面的情況還包括位于公共圖書(shū)館、醫(yī)院、網(wǎng)吧等公共場(chǎng)所的計(jì)算機(jī)。在這些地方,許多用戶(hù)共同使用幾臺(tái)計(jì)算機(jī),此時(shí)保護(hù)用戶(hù)的個(gè)人數(shù)據(jù)就顯得至關(guān)重要。
    同時(shí)應(yīng)用程序的良好設(shè)計(jì)與實(shí)現(xiàn)對(duì)用戶(hù)專(zhuān)業(yè)知識(shí)以及相關(guān)培訓(xùn)要求少之又少。

    讓我們來(lái)看一下現(xiàn)實(shí)世界中一個(gè)完美的Web應(yīng)用是怎樣工作的:
    1. 用戶(hù)在瀏覽器中輸入U(xiǎn)RL,訪問(wèn)一個(gè)頁(yè)面。
    2. Web應(yīng)用顯示一個(gè)登陸頁(yè)面,要求用戶(hù)輸入有效的驗(yàn)證信息。
    3. 用戶(hù)輸入用戶(hù)名和密碼。
    4. 假設(shè)用戶(hù)提供的驗(yàn)證信息是正確的,經(jīng)過(guò)了驗(yàn)證過(guò)程,Web應(yīng)用允許用戶(hù)瀏覽他有權(quán)訪問(wèn)的區(qū)域。
    5. 退出時(shí),用戶(hù)點(diǎn)擊頁(yè)面的退出按鈕,Web應(yīng)用顯示確認(rèn)頁(yè)面,詢(xún)問(wèn)用戶(hù)是否真的需要退出。一旦用戶(hù)點(diǎn)擊確定按鈕,Session結(jié)束,Web應(yīng)用重新定位到登陸頁(yè)面。用戶(hù)現(xiàn)在可以放心的離開(kāi)而不用擔(dān)心他的信息會(huì)被泄露。
    6. 另一個(gè)用戶(hù)坐到了同一臺(tái)電腦前。他點(diǎn)擊后退按鈕,Web應(yīng)用不應(yīng)該顯示上一個(gè)用戶(hù)訪問(wèn)過(guò)的任何一個(gè)頁(yè)面。
    事實(shí)上,Web應(yīng)用將一直停留在登陸頁(yè)面上,除非第二個(gè)用戶(hù)提供正確的驗(yàn)證信息,之后才可以訪問(wèn)他有權(quán)限的區(qū)域。

    通過(guò)示例程序,文章向您闡述了如何在一個(gè)Web應(yīng)用中實(shí)現(xiàn)上面的功能。




    一. JSP samples

    為了更為有效地向您說(shuō)明這個(gè)解決方案,本文將從展示一個(gè)Web應(yīng)用logoutSampleJSP1中碰到的問(wèn)題開(kāi)始。這個(gè)示例代表了許多沒(méi)有正確解決退出過(guò)程的Web應(yīng)用。logoutSampleJSP1包含一下JSP頁(yè)面:login.jsp,  home.jsp,  secure1.jsp,  secure2.jsp,  logout.jsp,  loginAction.jsp, 和 logoutAction.jsp。其中頁(yè)面home.jsp,  secure1.jsp,  secure2.jsp, 和 logout.jsp是不允許未經(jīng)認(rèn)證的用戶(hù)訪問(wèn)的,也就是說(shuō),這些頁(yè)面包含了重要信息,在用戶(hù)登陸之前或者退出之后都不應(yīng)該顯示在瀏覽器中。login.jsp頁(yè)面包含了用于用戶(hù)輸入用戶(hù)名和密碼的form。logout.jsp頁(yè)面包含了要求用戶(hù)確認(rèn)是否退出的form。loginAction.jsp和logoutAction.jsp作為控制器分別包含了登陸和退出動(dòng)作的代碼。

    第二個(gè)Web示例應(yīng)用logoutSampleJSP2展示了如何糾正示例logoutSampleJSP1中的問(wèn)題。但是第二個(gè)示例logoutSampleJSP2自身也是有問(wèn)題的。在特定情況下,退出問(wèn)題依然存在。

    第三個(gè)Web示例應(yīng)用logoutSampleJSP3對(duì)logoutSampleJSP2進(jìn)行了改進(jìn),比較妥善地解決了退出問(wèn)題。

    最后一個(gè)Web示例logoutSampleStruts展示了JakartaStruts如何優(yōu)雅地解決退出問(wèn)題。

    注意:本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant瀏覽器上測(cè)試通過(guò)。



    二. Login action

    Brian Pontarelli的經(jīng)典文章
    《J2EE Security: Container Versus Custom》 討論了不同的J2EE認(rèn)證方法。文章同時(shí)指出,HTTP協(xié)議和基于form的認(rèn)證方法并不能提供處理用戶(hù)退出問(wèn)題的機(jī)制。因此,解決方法便是引入用戶(hù)自定義的安全實(shí)現(xiàn)機(jī)制,這就提供了更大的靈活性。

    在用戶(hù)自定義的認(rèn)證方法中,普遍采用的方法是從用戶(hù)提交的form中獲得用戶(hù)輸入的認(rèn)證信息,然后到諸如LDAP (lightweight directory access protocol)或關(guān)系數(shù)據(jù)庫(kù)(relational database management system, RDBMS)的安全域中進(jìn)行認(rèn)證。如果用戶(hù)提供的認(rèn)證信息是有效的,登陸動(dòng)作在HttpSession對(duì)象中保存某個(gè)對(duì)象。HttpSession存在著保存的對(duì)象則表示用戶(hù)已經(jīng)登陸到Web應(yīng)用當(dāng)中。為了方便起見(jiàn),本文所附的示例只在HttpSession中保存一個(gè)用戶(hù)名以表明用戶(hù)已經(jīng)登陸。清單1是從loginAction.jsp頁(yè)面中節(jié)選的一段代碼以此講解登陸動(dòng)作:




    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);
    //...
    				
    
    


    本文當(dāng)中所附Web應(yīng)用示例均以關(guān)系型數(shù)據(jù)庫(kù)作為安全域,但本問(wèn)所講述的內(nèi)容同樣適用于其他任何類(lèi)型的安全域。



    三. Logout action

    退出動(dòng)作包含刪除用戶(hù)名以及調(diào)用用戶(hù)的HttpSession對(duì)象的invalidate()方法。清單2是從loginoutAction.jsp中節(jié)選的一段代碼,以此說(shuō)明退出動(dòng)作:



    Listing 2
    //...

    session.removeAttribute(
    "User" );
    session.invalidate();
    //...

    						
    
    


    四. 阻止未經(jīng)認(rèn)證訪問(wèn)受保護(hù)的JSP頁(yè)面

    從提交的form中獲取用戶(hù)提交的認(rèn)證信息并經(jīng)過(guò)驗(yàn)證后,登陸動(dòng)作僅僅在HttpSession對(duì)象中寫(xiě)入一個(gè)用戶(hù)名。退出動(dòng)作則剛好相反,它從HttpSession中刪除用戶(hù)名并調(diào)用HttpSession對(duì)象的invalidate()方法。為了使登陸和退出動(dòng)作真正發(fā)揮作用,所有受保護(hù)的JSP頁(yè)面必須首先驗(yàn)證HttpSession中包含的用戶(hù)名,以便確認(rèn)用戶(hù)當(dāng)前是否已經(jīng)登陸。如果HttpSession中包含了用戶(hù)名,就說(shuō)明用戶(hù)已經(jīng)登陸,Web應(yīng)用會(huì)將剩余的JSP頁(yè)中的動(dòng)態(tài)內(nèi)容發(fā)送給瀏覽器。否則,JSP頁(yè)將跳轉(zhuǎn)到登陸頁(yè)面,login.jsp。頁(yè)面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
    //...
    						
    
    


    在這個(gè)代碼段中,程序從HttpSession中檢索username字符串。如果username字符串為空,Web應(yīng)用則自動(dòng)中止執(zhí)行當(dāng)前頁(yè)面并跳轉(zhuǎn)到登陸頁(yè),同時(shí)給出錯(cuò)誤信息“Session has ended. Please log in.”;如果不為空,Web應(yīng)用繼續(xù)執(zhí)行,把剩余的頁(yè)面提供給用戶(hù),從而使JSP頁(yè)面的動(dòng)態(tài)內(nèi)容成為服務(wù)對(duì)象。



    五.運(yùn)行l(wèi)ogoutSampleJSP1

    運(yùn)行l(wèi)ogoutSampleJSP1將會(huì)出現(xiàn)如下幾種情形:

    • 如果用戶(hù)沒(méi)有登陸,Web應(yīng)用將會(huì)正確中止受保護(hù)頁(yè)面home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp中動(dòng)態(tài)內(nèi)容的執(zhí)行。也就是說(shuō),假如用戶(hù)并沒(méi)有登陸,但是在瀏覽器地址欄中直接敲入受保護(hù)JSP頁(yè)的地址試圖訪問(wèn),Web應(yīng)用將自動(dòng)跳轉(zhuǎn)到登陸頁(yè)面,同時(shí)顯示錯(cuò)誤信息“Session has ended.Please log in.”

    • 同樣的,當(dāng)一個(gè)用戶(hù)已經(jīng)退出,Web應(yīng)用將會(huì)正確中止受保護(hù)頁(yè)面home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp中動(dòng)態(tài)內(nèi)容的執(zhí)行。也就是說(shuō),用戶(hù)退出以后,如果在瀏覽器地址欄中直接敲入受保護(hù)JSP頁(yè)的地址試圖訪問(wèn),Web應(yīng)用將自動(dòng)跳轉(zhuǎn)到登陸頁(yè)面,同時(shí)顯示錯(cuò)誤信息“Session has ended.Please log in.”

    • 用戶(hù)退出以后,如果點(diǎn)擊瀏覽器上的后退按鈕返回到先前的頁(yè)面,Web應(yīng)用將不能正確保護(hù)受保護(hù)的JSP頁(yè)面——在Session銷(xiāo)毀后(用戶(hù)退出)受保護(hù)的JSP頁(yè)會(huì)重新顯示在瀏覽器中。然而,點(diǎn)擊該頁(yè)面上的任何鏈接,Web應(yīng)用都會(huì)跳轉(zhuǎn)到登陸頁(yè)面,同時(shí)顯示錯(cuò)誤信息“Session has ended.Please log in.”



    六. 阻止瀏覽器緩存
     
    上述問(wèn)題的根源就在于現(xiàn)代大部分瀏覽器都有一個(gè)后退按鈕。當(dāng)點(diǎn)擊后退按鈕時(shí),默認(rèn)情況下瀏覽器不會(huì)從Web服務(wù)器上重新獲取頁(yè)面,而是簡(jiǎn)單的從瀏覽器緩存中重新載入頁(yè)面。這個(gè)問(wèn)題并不僅限于基于Java(JSP/servlets/Struts) 的Web應(yīng)用當(dāng)中,在基于PHP (Hypertext Preprocessor)、ASP、(Active Server Pages)、和.NET的Web應(yīng)用中也同樣存在。

    在用戶(hù)點(diǎn)擊后退按鈕之后,瀏覽器到Web服務(wù)器(一般來(lái)說(shuō))或者應(yīng)用服務(wù)器(在java的情況下)再?gòu)姆?wù)器到瀏覽器這樣通常意義上的HTTP回路并沒(méi)有建立。僅僅只是用戶(hù),瀏覽器和緩存之間進(jìn)行了交互。所以即使受保護(hù)的JSP頁(yè)面,例如home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp包含了清單3上的代碼,當(dāng)點(diǎn)擊后退按鈕時(shí),這些代碼也永遠(yuǎn)不會(huì)執(zhí)行的。

    緩存的好壞,真是仁者見(jiàn)仁智者見(jiàn)智。緩存事實(shí)上的確提供了一些便利,但這些便利通常只存在于靜態(tài)的HTML頁(yè)面或基于圖形或影像的頁(yè)面。而另一方面,Web應(yīng)用通常是面向數(shù)據(jù)的。由于Web應(yīng)用中的數(shù)據(jù)頻繁變更,所以與為了節(jié)省時(shí)間從緩存中讀取并顯示過(guò)期的數(shù)據(jù)相比,提供最新的數(shù)據(jù)顯得尤為重要!

    幸運(yùn)的是,HTTP頭信息“Expires”和“Cache-Control”為應(yīng)用程序服務(wù)器提供了一個(gè)控制瀏覽器和代理服務(wù)器上緩存的機(jī)制。HTTP頭信息Expires告訴代理服務(wù)器它的緩存頁(yè)面何時(shí)將過(guò)期。HTTP1.1規(guī)范中新定義的頭信息Cache-Control在Web應(yīng)用當(dāng)中可以通知瀏覽器不緩存任何頁(yè)面。當(dāng)點(diǎn)擊后退按鈕時(shí),瀏覽器發(fā)送Http請(qǐng)求道應(yīng)用服務(wù)器以便獲取該頁(yè)面的最新拷貝。如下是使用Cache-Control的基本方法:

    • no-cache:強(qiáng)制緩存從服務(wù)器上獲取該頁(yè)面的最新拷貝
    • no-store: 在任何情況下緩存不保存該頁(yè)面

    HTTP1.0規(guī)范中的Pragma:no-cache等同于HTTP1.1規(guī)范中的Cache-Control:no-cache,同樣可以包含在頭信息中。

    通過(guò)使用HTTP頭信息的cache控制,第二個(gè)示例應(yīng)用logoutSampleJSP2解決了logoutSampleJSP1的問(wèn)題。logoutSampleJSP2與logoutSampleJSP1不同表現(xiàn)在如下代碼段中,這一代碼段加入進(jìn)所有受保護(hù)的頁(yè)面中:




    //...
    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);
    }
    //...

    						
    
    


    通過(guò)設(shè)置頭信息和檢查HttpSession對(duì)象中的用戶(hù)名來(lái)確保瀏覽器不會(huì)緩存JSP頁(yè)面。同時(shí),如果用戶(hù)未登陸,JSP頁(yè)面的動(dòng)態(tài)內(nèi)容不會(huì)發(fā)送到瀏覽器,取而代之的將是登陸頁(yè)面login.jsp。



    七. 運(yùn)行l(wèi)ogoutSampleJSP2

    運(yùn)行Web示例應(yīng)用logoutSampleJSP2后將會(huì)看到如下結(jié)果:

    • 當(dāng)用戶(hù)退出后試圖點(diǎn)擊后退按鈕,瀏覽器不會(huì)重新顯示受保護(hù)的頁(yè)面,它只會(huì)顯示登陸頁(yè)login.jsp同時(shí)給出提示信息Session has ended. Please log in.

    • 然而,當(dāng)按了后退按鈕返回的頁(yè)是處理用戶(hù)提交數(shù)據(jù)的頁(yè)面時(shí),IE和Avant瀏覽器將彈出如下信息提示:

               警告:頁(yè)面已過(guò)期
               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瀏覽器將會(huì)顯示一個(gè)對(duì)話(huà)框,提示信息如下:

                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瀏覽器中選擇重新發(fā)送數(shù)據(jù)后,前一個(gè)JSP頁(yè)面將重新顯示在瀏覽器中。顯然的,這病不是我們所想看到的因?yàn)樗`背了logout動(dòng)作的目的。發(fā)生這一現(xiàn)象時(shí),很可能是一個(gè)惡意用戶(hù)在嘗試獲取其他用戶(hù)的數(shù)據(jù)。然而,這個(gè)問(wèn)題僅僅出現(xiàn)在點(diǎn)擊后退按鈕后,瀏覽器返回到一個(gè)處理POST請(qǐng)求的頁(yè)面。



    八. 記錄最后登陸時(shí)間

    上述問(wèn)題的發(fā)生是因?yàn)闉g覽器重新提交了其緩存中的數(shù)據(jù)。這本文的例子中,數(shù)據(jù)包含了用戶(hù)名和密碼。盡管IE瀏覽器給出了安全警告信息,但事實(shí)上瀏覽器此時(shí)起到了負(fù)面作用。

    為了解決logoutSampleJSP2中出現(xiàn)的問(wèn)題,logoutSampleJSP3的login.jsp除了包含username和password的之外,還增加了一個(gè)稱(chēng)作lastLogon的隱藏表單域,此表單域?qū)?huì)動(dòng)態(tài)的被初始化為一個(gè)long型值。這個(gè)long型值是通過(guò)調(diào)用System.currentTimeMillis()獲取到的自1970年1月1日以來(lái)的毫秒數(shù)。當(dāng)login.jsp中的form提交時(shí),loginAction.jsp首先將隱藏域中的值與用戶(hù)數(shù)據(jù)庫(kù)中的lastLogon值進(jìn)行比較。只有當(dāng)lastLogon表單域中的值大于數(shù)據(jù)庫(kù)中的值時(shí)Web應(yīng)用才認(rèn)為這是個(gè)有效的登陸。

    為了驗(yàn)證登陸,數(shù)據(jù)庫(kù)中l(wèi)astLogon字段必須用表單中的lastLogon值進(jìn)行更新。上例中,當(dāng)瀏覽器重復(fù)提交緩存中的數(shù)據(jù)時(shí),表單中的lastLogon值不比數(shù)據(jù)庫(kù)中的lastLogon值大,因此,loginAction將跳轉(zhuǎn)到login.jsp頁(yè)面,并顯示如下錯(cuò)誤信息“Session has ended.Please log in.”清單5是loginAction中節(jié)選的代碼段:




    清單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);
    //...
    						
    
    


    為了實(shí)現(xiàn)上述方法,你必須記錄每個(gè)用戶(hù)的最后登陸時(shí)間。對(duì)于采用關(guān)系型數(shù)據(jù)庫(kù)安全域來(lái)說(shuō),這點(diǎn)可以可以通過(guò)在某個(gè)表中加上lastLogin字段輕松實(shí)現(xiàn)。雖然對(duì)LDAP以及其他的安全域來(lái)說(shuō)需要稍微動(dòng)下腦筋,但最后登陸方法很顯然是可以實(shí)現(xiàn)的。

    表示最后登陸時(shí)間的方法有很多。示例logoutSampleJSP3利用了自1970年1月1日以來(lái)的毫秒數(shù)。這個(gè)方法即使在許多人在不同瀏覽器中用一個(gè)用戶(hù)賬號(hào)登陸時(shí)也是可行的。



    九. 運(yùn)行l(wèi)ogoutSampleJSP3

    運(yùn)行示例logoutSampleJSP3將展示如何正確處理退出問(wèn)題。一旦用戶(hù)退出,點(diǎn)擊瀏覽器上的后退按鈕在任何情況下都不會(huì)在瀏覽器中顯示受保護(hù)的JSP頁(yè)面。這個(gè)示例展示了如何正確處理退出問(wèn)題而不需要對(duì)用戶(hù)進(jìn)行額外的培訓(xùn)。

    為了使代碼更簡(jiǎn)練有效,一些冗余的代碼可以剔除。一種途徑就是把清單4中的代碼寫(xiě)到一個(gè)單獨(dú)的JSP頁(yè)中,其他JSP頁(yè)面可以通過(guò)標(biāo)簽
    <jsp:include>進(jìn)行使用



    十. Struts框架下的退出實(shí)現(xiàn)

    與直接使用JSP或JSP/servlets進(jìn)行Web應(yīng)用開(kāi)發(fā)相比,另一個(gè)更好的可選方案是使用Struts。對(duì)于一個(gè)基于Struts的Web應(yīng)用來(lái)說(shuō),添加一個(gè)處理退出問(wèn)題的框架可以?xún)?yōu)雅地不費(fèi)氣力的實(shí)現(xiàn)。這歸功于Struts是采用MVC設(shè)計(jì)模式的,因此可以將模型和視圖代碼清晰的分離。另外,Java是一個(gè)面向?qū)ο蟮恼Z(yǔ)言,支持繼承,可以比JSP中的腳本更為容易地實(shí)現(xiàn)代碼重用。對(duì)于Struts來(lái)說(shuō),清單4中的代碼可以從JSP頁(yè)面中移植到Action類(lèi)的execute()方法中。

    此外,我們還可以定義一個(gè)繼承Struts Action類(lèi)的Action基類(lèi),其execute()方法中包含了類(lèi)似清單4中的代碼。通過(guò)繼承,其他Action類(lèi)可以繼承基本類(lèi)中的通用邏輯來(lái)設(shè)置HTTP頭信息以及檢索HttpSession對(duì)象中的username字符串。這個(gè)Action基類(lèi)是一個(gè)抽象類(lèi)并定義了一個(gè)抽象方法executeAction()。所有繼承自Action基類(lèi)的子類(lèi)都必須實(shí)現(xiàn)exectuteAction()方法而不是覆蓋它。通過(guò)繼承這一機(jī)制,所有繼承自Action基類(lèi)的子類(lèi)都不必再擔(dān)心退出代碼接口。(plumbing實(shí)在不知道怎么翻譯了,^+^,高手幫幫忙啊!原文:With this inheritance hierarchy in place, all of the base Action's subclasses no longer need to worry about any plumbing logout code.)。他們將只包含正常的業(yè)務(wù)邏輯代碼。清單6是基類(lèi)的部分代碼:



    清單6
    publicabstractclass BaseAction extends Action {
        public ActionForward execute(ActionMapping mapping, ActionForm form,
                                                     HttpServletRequest request, HttpServletResponse response) 
                   throws IOException, ServletException {

    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

    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中的很相像,唯一區(qū)別是用ActionMapping findForward替代了RequestDispatcher forward。清單6中,如果在HttpSession中未找到username字符串,ActionMapping對(duì)象將找到名為sessionEnded的forward元素并跳轉(zhuǎn)到對(duì)應(yīng)的path。如果找到了,子類(lèi)通過(guò)實(shí)現(xiàn)executeAction()方法,將執(zhí)行他們自己的業(yè)務(wù)邏輯。因此,在struts-web.xml配置文件中為所有繼承自Action基類(lèi)的子類(lèi)聲明個(gè)一名為sessionEnded的forward元素并將其指向login.jsp是至關(guān)重要的。清單7以secure1 action闡明了這樣一個(gè)聲明:


    清單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類(lèi)的子類(lèi)Secure1Action實(shí)現(xiàn)了executeAction()方法而不是覆蓋它。Secure1Action類(lèi)不需要執(zhí)行任何退出代碼,如清單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" ));
             }
    }
    						
    
    


    上面的解決方案是如此的優(yōu)雅有效,它僅僅只需要定義一個(gè)基類(lèi)而不需要額外的代碼工作。將通用的行為方法寫(xiě)成一個(gè)繼承StrutsAction的基類(lèi)是者的推薦的,而且這是許多Struts項(xiàng)目的共同經(jīng)驗(yàn)。




    十一. 局限性

    上述解決方案對(duì)JSP或基于Struts的Web應(yīng)用都是非常簡(jiǎn)單而實(shí)用的,但它還是有某些局限。在我看來(lái),這些局限并不是至關(guān)緊要的。

    •   通過(guò)取消與瀏覽器后退按鈕有關(guān)的緩存機(jī)制,一旦用戶(hù)離開(kāi)頁(yè)面而沒(méi)有對(duì)數(shù)據(jù)進(jìn)行提交,那么頁(yè)面將會(huì)丟失所有輸入的數(shù)據(jù)。即使點(diǎn)擊瀏覽器的后退按鈕返回到剛才的頁(yè)面也無(wú)濟(jì)于事,因?yàn)闉g覽器會(huì)從服務(wù)器獲取新的空白頁(yè)面顯示出來(lái)。一種可能的方法并不是阻止這些JSP頁(yè)面包含數(shù)據(jù)數(shù)據(jù)表格。在基于JSP的解決方案當(dāng)中,那些JSP頁(yè)面可以刪除在清單4中的代碼。在基于Struts的解決方案當(dāng)中,Action類(lèi)需要繼承自Struts的Action類(lèi)而非BaseAction類(lèi)。

    •  上面講述的方法在Opera瀏覽器中不能工作。事實(shí)上沒(méi)有適用于Opera瀏覽器的解決方案,因?yàn)镺pera瀏覽器與2616 Hypertext Transfer Protocol—HTTP/1.1緊密相關(guān)。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.

    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.

    幸運(yùn)的是,使用微軟的IE和基于Mozilla的瀏覽器用戶(hù)多余Opera瀏覽器。上面講述的解決方案對(duì)大多數(shù)用戶(hù)來(lái)說(shuō)還是有幫助的。另外,無(wú)論是否使用上述的解決方案,Opera瀏覽器仍然存在用戶(hù)退出問(wèn)題,就Opera來(lái)說(shuō)沒(méi)有任何改變。然而,正如RFC2616中所說(shuō),通過(guò)像上面一樣設(shè)置頭文件指令,當(dāng)用戶(hù)點(diǎn)擊一個(gè)鏈接時(shí),Opera瀏覽器不會(huì)從緩存中獲取頁(yè)面。




    十二. 結(jié)論

    這篇文章講述了處理退出問(wèn)題的解決方案,盡管方案簡(jiǎn)單的令人驚訝,但在所有情況下都能有效地工作。無(wú)論是對(duì)JSP還是Struts,所要做的不過(guò)是寫(xiě)一段不超過(guò)50行的代碼以及一個(gè)記錄用戶(hù)最后登陸時(shí)間的方法。在有密碼保護(hù)的Web應(yīng)用中使用這些方案能夠確保在任何情況下用戶(hù)的私人數(shù)據(jù)不致泄露,同時(shí),也能增加用戶(hù)的經(jīng)驗(yàn)。
    posted on 2007-08-06 13:17 守望者 閱讀(265) 評(píng)論(1)  編輯  收藏

    只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 亚洲色偷偷偷网站色偷一区| ww在线观视频免费观看w| 亚洲国产无套无码av电影| 成人毛片免费网站| 99爱视频99爱在线观看免费| 九九九精品视频免费| 亚洲av无码一区二区三区天堂| 亚洲成人黄色在线观看| 久久亚洲国产成人精品性色| 亚洲自偷自偷图片| 亚洲黄黄黄网站在线观看| 日韩一区二区免费视频| 青青视频观看免费99| 最近免费中文字幕mv在线电影| 中文字幕免费在线看电影大全| 免费人成又黄又爽的视频在线电影| 亚洲三级高清免费| 亚洲国产成人无码av在线播放| 久久精品国产亚洲av日韩| 国产AV无码专区亚洲AVJULIA| 久久久久亚洲精品天堂久久久久久 | 国产精品青草视频免费播放| 老司机午夜性生免费福利| 亚洲欧美日本韩国| 中国亚洲呦女专区| 亚洲永久网址在线观看| 亚洲免费福利在线视频| 亚洲人成图片网站| 亚洲欧美中文日韩视频| 亚洲精品又粗又大又爽A片| 亚洲熟妇AV一区二区三区宅男| 亚洲av专区无码观看精品天堂| 亚洲人成影院午夜网站| 亚洲一卡二卡三卡| 97se亚洲国产综合自在线| 亚洲午夜无码毛片av久久京东热 | 妞干网免费观看视频| 成年女人视频网站免费m| 国内外成人免费视频| 国产高清在线免费视频| 五月婷婷亚洲综合|