wolfmanchen 翻譯javaworld.com上名為<<Solving the logout PRoblem properly and elegantly>>的文章
原文地址: http://www.javaworld.com/javaworld/jw-09-2004/jw-0927-logout.Html
正確優(yōu)雅的解決用戶退出問(wèn)題——jsp和Struts解決方案
摘要
在一個(gè)有密碼保護(hù)的Web應(yīng)用中,正確處理用戶退出過(guò)程并不僅僅只需調(diào)用Httpsession的invalidate()方法。現(xiàn)在大部分瀏覽器上都有后退和前進(jìn)按鈕,答應(yīng)用戶后退或前進(jìn)到一個(gè)頁(yè)面。假如在用戶在退出一個(gè)Web應(yīng)用后按了后退按鈕瀏覽器把緩存中的頁(yè)面呈現(xiàn)給用戶,這會(huì)使用戶產(chǎn)生迷惑,他們會(huì)開(kāi)始擔(dān)心他們的個(gè)人數(shù)據(jù)是否安全。許多Web應(yīng)用強(qiáng)迫用戶退出時(shí)關(guān)閉整個(gè)瀏覽器,這樣,用戶就無(wú)法點(diǎn)擊后退按鈕了。還有一些使用javascript,但在某些客戶端瀏覽器這卻不一定起作用。這些解決方案都很笨拙且不能保證在任一情況下100%有效,同時(shí),它也要求用戶有一定的操作經(jīng)驗(yàn)。
這篇文章以示例闡述了正確解決用戶退出問(wèn)題的方案。作者Kevin Le首先描述了一個(gè)密碼保護(hù)Web應(yīng)用,然后以示例程序解釋問(wèn)題如何產(chǎn)生并討論解決問(wèn)題的方案。文章雖然是針對(duì)JSP頁(yè)面進(jìn)行闡述,但作者所闡述的概念很輕易理解切能夠?yàn)槠渌鸚eb技術(shù)所采用。最后作者展示了如何用Jakarta Struts優(yōu)雅地解決這一問(wèn)題。
大部分Web應(yīng)用不會(huì)包含象銀行賬戶或信用卡資料那樣機(jī)密的信息,但一旦涉及到敏感數(shù)據(jù),我們就需要提供一類(lèi)密碼保護(hù)機(jī)制。舉例來(lái)說(shuō),一個(gè)工廠中工人通過(guò)Web訪問(wèn)他們的時(shí)間安排、進(jìn)入他們的練習(xí)課程以及查看他們的薪金等等。此時(shí)應(yīng)用SSL(Secure Socket Layer)有點(diǎn)殺雞用牛刀的感覺(jué),但不可否認(rèn),我們又必須為這些應(yīng)用提供密碼保護(hù),否則,工人(也就是Web應(yīng)用的使用者)可以窺探到工廠中其他雇員的私人機(jī)密信息。
與上述情形相似的還有位處圖書(shū)館、醫(yī)院等公共場(chǎng)所的計(jì)算機(jī)。在這些地方,許多用戶共同使用幾臺(tái)計(jì)算機(jī),此時(shí)保護(hù)用戶的個(gè)人數(shù)據(jù)就顯得至關(guān)重要。設(shè)計(jì)良好編寫(xiě)優(yōu)秀的應(yīng)用對(duì)用戶專(zhuān)業(yè)知識(shí)的要求少之又少。
我們來(lái)看一下現(xiàn)實(shí)世界中一個(gè)完美的Web應(yīng)用是如何表現(xiàn)的:一個(gè)用戶通過(guò)瀏覽器訪問(wèn)一個(gè)頁(yè)面。Web應(yīng)用展現(xiàn)一個(gè)登陸頁(yè)面要求用戶輸入有效的驗(yàn)證信息。用戶輸入了用戶名和密碼。此時(shí)我們假設(shè)用戶提供的身份驗(yàn)證信息是正確的,經(jīng)過(guò)了驗(yàn)證過(guò)程,Web應(yīng)用答應(yīng)用戶瀏覽他有權(quán)訪問(wèn)的區(qū)域。用戶想退出時(shí),點(diǎn)擊退出按鈕,Web應(yīng)用要求用戶確認(rèn)他是否則真的需要退出,假如用戶確定退出,Session結(jié)束,Web應(yīng)用重新定位到登陸頁(yè)面。用戶可以放心的離開(kāi)而不用擔(dān)心他的信息會(huì)泄露。另一個(gè)用戶坐到了同一臺(tái)電腦前,他點(diǎn)擊后退按鈕,Web應(yīng)用不應(yīng)該出現(xiàn)上一個(gè)用戶訪問(wèn)過(guò)的任何一個(gè)頁(yè)面。事實(shí)上,Web應(yīng)用在第二個(gè)用戶提供正確的驗(yàn)證信息之前應(yīng)當(dāng)一直停留在登陸頁(yè)面上。
通過(guò)示例程序,文章向您闡述了如何在一個(gè)Web應(yīng)用中實(shí)現(xiàn)這一功能。
JSP samples
為了更為有效地闡述實(shí)現(xiàn)方案,本文將從展示一個(gè)示例應(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, and logoutAction.jsp。其中頁(yè)面home.jsp, secure1.jsp, secure2.jsp, 和logout.jsp是不答應(yīng)未經(jīng)認(rèn)證的用戶訪問(wèn)的,也就是說(shuō),這些頁(yè)面包含了重要信息,在用戶登陸之前或者退出之后都不應(yīng)該出現(xiàn)在瀏覽器中。login.jsp包含了用于用戶輸入用戶名和密碼的form。logout.jsp頁(yè)包含了要求用戶確認(rèn)是否退出的form。loginAction.jsp和logoutAction.jsp作為控制器分別包含了登陸和退出代碼。
第二個(gè)示例應(yīng)用logoutSampleJSP2展示了如何解決示例logoutSampleJSP1中的問(wèn)題。然而,第二個(gè)應(yīng)用自身也是有疑問(wèn)的。在特定的情況下,退出問(wèn)題還是會(huì)出現(xiàn)。
第三個(gè)示例應(yīng)用logoutSampleJSP3在第二個(gè)示例上進(jìn)行了改進(jìn),比較完善地解決了退出問(wèn)題。
最后一個(gè)示例logoutSampleStruts展示了Struts如何美麗地解決登陸問(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)證并未提供處理用戶退出的機(jī)制。因此,解決途徑便是引入自定義的安全實(shí)現(xiàn)機(jī)制。
自定義的安全認(rèn)證機(jī)制普遍采用的方法是從form中獲得用戶輸入的認(rèn)證信息,然后到諸如LDAP (lightweight Directory access protocol)或關(guān)系數(shù)據(jù)庫(kù)的安全域中進(jìn)行認(rèn)證。假如用戶提供的認(rèn)證信息是有效的,登陸動(dòng)作往HttpSession對(duì)象中注入某個(gè)對(duì)象。HttpSession存在著注入的對(duì)象則表示用戶已經(jīng)登陸。為了方便讀者理解,本文所附的示例只往HttpSession中寫(xiě)入一個(gè)用戶名以表明用戶已經(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);
//...
Listing 2
//...
session.removeAttribute("User");
session.invalidate();
//...
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
//...
//...
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);
}
//...
清單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);
//...
清單6
public abstract class 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);
}
protected abstract ActionForward executeAction(ActionMapping mapping,
ActionForm form, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException;
private boolean userIsLoggedIn(HttpServletRequest request) {
if (request.getSession().getAttribute("User") == null) {
return false;
}
return true;
}
}
清單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>
清單8
public class 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"));
}
}
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注