??xml version="1.0" encoding="utf-8" standalone="yes"?>国产在线观看一区二区三区,一区二区在线影院,日韩成人18http://www.aygfsteel.com/wvpon/archive/2007/08/06/134681.html守望?/dc:creator>守望?/author>Mon, 06 Aug 2007 05:17:00 GMThttp://www.aygfsteel.com/wvpon/archive/2007/08/06/134681.htmlhttp://www.aygfsteel.com/wvpon/comments/134681.htmlhttp://www.aygfsteel.com/wvpon/archive/2007/08/06/134681.html#Feedback1http://www.aygfsteel.com/wvpon/comments/commentRss/134681.htmlhttp://www.aygfsteel.com/wvpon/services/trackbacks/134681.htmlhttp://www.blogjava.cn/
   正确优雅的解决用户退出问?br>                                           ------JSP和Struts解决Ҏ

摘要

在一个有密码保护的Web应用当中Q正妥善的处理用户退Eƈ不仅仅只需要调用HttpSession对象的invalidate()ҎQ因为现在大部分览器上都有后退(Back)和前q?Forward)按钮Q允许用户后退或前q到一个页面。在用户退Z个Web应用之后Q如果按了后退按钮Q浏览器把缓存中的页面呈现给用户Q这会用户产生疑惑Q他们会开始担心他们的个h数据是否安全?br>
实际上,许多Web应用会弹Z个页面,警告用户退出时关闭整个览器,以此来阻止用Ld退按钮。还有一些用JavaScriptQ但在某些客L览器中q却不一定v作用。这些解x案大多数实现都很W拙Q且不能保证在Q何情况下?00%有效Q同Ӟ它还要求用户有一定的操作l验?br>
q篇文章以简单的E序CZ阐述了正解决用户退出问题的Ҏ。作者Kevin Le首先描述了一个理想的密码保护Web应用Q然后以CZE序解释问题如何产生q讨决问题的Ҏ。文章虽然是针对JSPq行讨论阐述Q但作者所阐述的概念很Ҏ理解而且能够为其他Web技术所采用。最后最后,作者Kevin Le用Jakarta Struts更ؓ优雅地解决用户退出问题。文中包含JSP和Struts的示例程?(3,700 words; September 27, 2004)




大部分Web应用不会包含像银行̎h信用卡资料那h密的信息Q但是一旦涉及到敏感数据Q就需要我们提供某些密码保护机制。例如,在一个工厂当中,工h必须通过Web应用E序讉K他们的时间安排、进入他们的培训评以及查看他们的薪金等{。此时应用SSL(Secure Socket Layer)有些大材小用了(SSL面不会在缓存中保存Q关于SSL的讨论已l超出本文的范围)。但是这些应用又实需要某U密码保护措施,否则Q工人(在这U情况下Q也是Web应用的用者)可以发现工厂中所有员工的Uh机密信息?br>
cM上面的情况还包括位于公共图书馆、医院?font color=#000000>|吧
{公共场所的计机。在q些地方Q许多用户共同用几台计机Q此时保护用L个h数据显得至关重要?/font> 同时应用E序的良好设计与实现对用户专业知识以及相兛_训要求少之又?

让我们来看一下现实世界中一个完的Web应用是怎样工作的:
1. 用户在浏览器中输入URLQ访问一个页面?br>2. Web应用昄一个登陆页面,要求用户输入有效的验证信息?br>3. 用户输入用户名和密码?br>4. 假设用户提供的验证信息是正确的,l过了验证过E,Web应用允许用户览他有权访问的区域?br>5. 退出时Q用L击页面的退出按钮,Web应用昄认面Q询问用h否真的需要退出。一旦用Lȝ定按钮,Sessionl束QWeb应用重新定位到登陆页面。用L在可以放心的d而不用担心他的信息会被泄霌Ӏ?br>6. 另一个用户坐C同一台电脑前。他点击后退按钮QWeb应用不应该显CZ一个用戯问过的Q何一个页面?br>事实上,Web应用一直停留在登陆面上,除非W二个用h供正的验证信息Q之后才可以讉K他有权限的区域?br>
通过CZE序Q文章向您阐qC如何在一个Web应用中实C面的功能?br>



一. JSP samples

Z更ؓ有效地向您说明这个解x案,本文从展示一个Web应用logoutSampleJSP1中碰到的问题开始。这个示例代表了许多没有正确解决退E的Web应用。logoutSampleJSP1包含一下JSP面Qlogin.jsp,  home.jsp,  secure1.jsp,  secure2.jsp,  logout.jsp,  loginAction.jsp, ?logoutAction.jsp。其中页面home.jsp,  secure1.jsp,  secure2.jsp, ?logout.jsp是不允许未经认证的用戯问的Q也是_q些面包含了重要信息,在用L陆之前或者退Z后都不应该显C在览器中。login.jsp面包含了用于用戯入用户名和密码的form。logout.jsp面包含了要求用L认是否退出的form。loginAction.jsp和logoutAction.jsp作ؓ控制器分别包含了登陆和退出动作的代码?br>
W二个WebCZ应用logoutSampleJSP2展示了如何纠正示例logoutSampleJSP1中的问题。但是第二个CZlogoutSampleJSP2自n也是有问题的。在特定情况下,退出问题依然存在?br>
W三个WebCZ应用logoutSampleJSP3对logoutSampleJSP2q行了改q,比较妥善地解决了退出问题?br>
最后一个WebCZlogoutSampleStruts展示?font size=3>Jakarta
Struts如何优雅地解决退出问题?br>
注意Q本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant览器上试通过?br>


? Login action

Brian Pontarelli的经典文?《J2EE Security: Container Versus Custom?讨论了不同的J2EE认证Ҏ。文章同时指出,HTTP协议和基于form的认证方法ƈ不能提供处理用户退出问题的机制。因此,解决Ҏ便是引入用户自定义的安全实现机制Q这提供了更大的灵zL?br>
在用戯定义的认证方法中Q普遍采用的Ҏ是从用户提交的form中获得用戯入的认证信息Q然后到诸如LDAP (lightweight directory access protocol)或关pL据库(relational database management system, RDBMS)的安全域中进行认证。如果用h供的认证信息是有效的Q登陆动作在HttpSession对象中保存某个对象。HttpSession存在着保存的对象则表示用户已经登陆到Web应用当中。ؓ了方便v见,本文所附的CZ只在HttpSession中保存一个用户名以表明用户已l登陆。清?是从loginAction.jsp面中节选的一D代码以此讲解登陆动作:




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应用CZ均以关系型数据库作ؓ安全域,但本问所讲述的内容同样适用于其他Q何类型的安全域?br>


? Logout action

退出动作包含删除用户名以及调用用户的HttpSession对象的invalidate()Ҏ。清?是从loginoutAction.jsp中节选的一D代码,以此说明退出动作:



Listing 2
//...

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

						



? L未经认证讉K受保护的JSP面

从提交的form中获取用h交的认证信息q经q验证后Q登陆动作仅仅在HttpSession对象中写入一个用户名。退出动作则刚好相反Q它从HttpSession中删除用户名q调用HttpSession对象的invalidate()Ҏ。ؓ了登陆和退出动作真正发挥作用,所有受保护的JSP面必须首先验证HttpSession中包含的用户名,以便认用户当前是否已经登陆。如果HttpSession中包含了用户名,p明用户已l登陆,Web应用会将剩余的JSP中的动态内容发送给览器。否则,JSP将跌{到登陆页面,login.jsp。页面home.jsp,  secure1.jsp,  secure2.jsp?logout.jsp均包含清?中的代码D:


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



在这个代码段中,E序从HttpSession?font color=#000000>?/font>username字符丌Ӏ如果username字符串ؓI,Web应用则自动中止执行当前页面ƈ跌{到登陆页Q同时给出错误信?#8220;Session has ended. Please log in.”Q如果不为空QWeb应用l箋执行Q把剩余的页面提供给用户Q从而JSP面的动态内Ҏ为服务对象?br>


?q行logoutSampleJSP1

q行logoutSampleJSP1会出现如下几种情ŞQ?br>
• 如果用户没有登陆QWeb应用会正确中止受保护页面home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp中动态内容的执行。也是_假如用户q没有登陆,但是在浏览器地址栏中直接敲入受保护JSP늚地址试图讉KQWeb应用自动蟩转到登陆面Q同时显C错误信?#8220;Session has ended.Please log in.”

• 同样的,当一个用户已l退出,Web应用会正确中止受保护页面home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp中动态内容的执行。也是_用户退Z后,如果在浏览器地址栏中直接敲入受保护JSP늚地址试图讉KQWeb应用自动蟩转到登陆面Q同时显C错误信?#8220;Session has ended.Please log in.”

• 用户退Z后,如果点击览器上的后退按钮q回到先前的面QWeb应用不能正保护受保护的JSP面——在Session销毁后Q用户退出)受保护的JSP会重新昄在浏览器中。然而,点击该页面上的Q何链接,Web应用都会跌{到登陆页面,同时昄错误信息“Session has ended.Please log in.”



? L览器缓?/font>
 
上述问题的根源就在于C大部分浏览器都有一个后退按钮。当点击后退按钮Ӟ默认情况下浏览器不会从Web服务器上重新获取面Q而是单的从浏览器~存中重新蝲入页面。这个问题ƈ不仅限于ZJava(JSP/servlets/Struts) 的Web应用当中Q在ZPHP (Hypertext Preprocessor)、ASP?Active Server Pages)、和.NET的Web应用中也同样存在?br>
在用Ld退按钮之后Q浏览器到Web服务器(一般来_或者应用服务器Q在java的情况下Q再从服务器到浏览器q样通常意义上的HTTP回\q没有徏立。仅仅只是用P览器和~存之间q行了交互。所以即使受保护的JSP面Q例如home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp包含了清?上的代码Q当点击后退按钮Ӟq些代码也永q不会执行的?br>
~存的好坏,真是仁者见仁智者见智。缓存事实上的确提供了一些便利,但这些便利通常只存在于静态的HTML面或基于图形或影像的页面。而另一斚wQWeb应用通常是面向数据的。由于Web应用中的数据频繁变更Q所以与Z节省旉从缓存中dq显C期的数据相比Q提供最新的数据昑־ؓ重要Q?br>
q运的是QHTTP头信?#8220;Expires”?#8220;Cache-Control”为应用程序服务器提供了一个控制浏览器和代理服务器上缓存的机制。HTTP头信息Expires告诉代理服务器它的缓存页面何时将q期。HTTP1.1规范中新定义的头信息Cache-Control在Web应用当中可以通知览器不~存M面。当点击后退按钮Ӟ览器发送Httph道应用服务器以便获取该页面的最新拷贝。如下是使用Cache-Control的基本方法:

• no-cache:强制~存从服务器上获取该面的最新拷?br>• no-store: 在Q何情况下~存不保存该面

HTTP1.0规范中的Pragma:no-cache{同于HTTP1.1规范中的Cache-Control:no-cacheQ同样可以包含在头信息中?

通过使用HTTP头信息的cache控制Q第二个CZ应用logoutSampleJSP2解决了logoutSampleJSP1的问题。logoutSampleJSP2与logoutSampleJSP1不同表现在如下代码段中,q一代码D加入进所有受保护的页面中Q?br>



//...
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?br>


? q行logoutSampleJSP2

q行WebCZ应用logoutSampleJSP2后将会看到如下结果:

• 当用户退出后试图点击后退按钮Q浏览器不会重新昄受保护的面Q它只会昄登陆login.jsp同时l出提示信息Session has ended. Please log in.

• 然而,当按了后退按钮q回的页是处理用h交数据的面ӞIE和Avant览器将弹出如下信息提示Q?br>
           警告Q页面已q期
           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览器将会显CZ个对话框Q提CZ息如下:

            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览器中选择h或者在Mozilla和FireFox览器中选择重新发送数据后Q前一个JSP面重新显C在览器中。显然的Q这病不是我们所想看到的因ؓ它违背了logout动作的目的。发生这一现象Ӟ很可能是一个恶意用户在试获取其他用户的数据。然而,q个问题仅仅出现在点d退按钮后,览器返回到一个处理POSTh的页面?br>


? 记录最后登陆时?

上述问题的发生是因ؓ览器重新提交了其缓存中的数据。这本文的例子中Q数据包含了用户名和密码。尽IE览器给Z安全警告信息Q但事实上浏览器此时起到了负面作用?br>
Z解决logoutSampleJSP2中出现的问题QlogoutSampleJSP3的login.jsp除了包含username和password的之外,q增加了一个称作lastLogon的隐藏表单域Q此表单域将会动态的被初始化Z个long型倹{这个long型值是通过调用System.currentTimeMillis()获取到的?970q??日以来的毫秒数。当login.jsp中的form提交ӞloginAction.jsp首先隐藏域中的g用户数据库中的lastLogonD行比较。只有当lastLogon表单域中的值大于数据库中的值时Web应用才认是个有效的登陆?br>
Z验证登陆Q数据库中lastLogon字段必须用表单中的lastLogonD行更新。上例中Q当览器重复提交缓存中的数据时Q表单中的lastLogong比数据库中的lastLogon值大Q因此,loginAction蟩转到login.jsp面Qƈ昄如下错误信息“Session has ended.Please log in.”清单5是loginAction中节选的代码D:




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



Z实现上述ҎQ你必须记录每个用户的最后登陆时间。对于采用关pd数据库安全域来说Q这点可以可以通过在某个表中加上lastLogin字段L实现。虽然对LDAP以及其他的安全域来说需要稍微动下脑{,但最后登陆方法很昄是可以实现的?br>
表示最后登陆时间的Ҏ有很多。示例logoutSampleJSP3利用了自1970q??日以来的毫秒数。这个方法即使在许多人在不同览器中用一个用戯̎L陆时也是可行的?br>


? q行logoutSampleJSP3

q行CZlogoutSampleJSP3展C如何正处理退出问题。一旦用户退出,点击览器上的后退按钮在Q何情况下都不会在览器中昄受保护的JSP面。这个示例展CZ如何正确处理退出问题而不需要对用户q行额外的培训?br>
Z使代码更l有效,一些冗余的代码可以剔除。一U途径是把清?中的代码写到一个单独的JSP中Q其他JSP面可以通过标签
<jsp:include>q行使用 ?br>


? Struts框架下的退出实?/font>

与直接用JSP或JSP/servletsq行Web应用开发相比,另一个更好的可选方案是使用Struts。对于一个基于Struts的Web应用来说Q添加一个处理退出问题的框架可以优雅CҎ力的实现。这归功于Struts是采用MVC设计模式的,因此可以模型和视图代码清晰的分R另外,Java是一个面向对象的语言Q支持承,可以比JSP中的脚本更ؓҎ地实C码重用。对于Struts来说Q清?中的代码可以从JSP面中移植到Actioncȝexecute()Ҏ中?br>
此外Q我们还可以定义一个承Struts ActioncȝAction基类Q其execute()Ҏ中包含了cM清单4中的代码。通过l承Q其他Actioncd以承基本类中的通用逻辑来设|HTTP头信息以及检索HttpSession对象中的username字符丌Ӏ这个Action基类是一个抽象类q定义了一个抽象方法executeAction()。所有承自Action基类的子c都必须实现exectuteAction()Ҏ而不是覆盖它。通过l承q一机制Q所有承自Action基类的子c都不必再担心退Z码接口。(plumbing实在不知道怎么译了,^+^Q高手帮帮忙啊!原文QWith this inheritance hierarchy in place, all of the base Action's subclasses no longer need to worry about any plumbing logout code.Q。他们将只包含正常的业务逻辑代码。清?是基cȝ部分代码Q?br>


清单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中的代码与清?中的很相像,唯一区别是用ActionMapping findForward替代了RequestDispatcher forward。清?中,如果在HttpSession中未扑ֈusername字符ԌActionMapping对象找到名为sessionEnded的forward元素q蟩转到对应的path。如果找CQ子c通过实现executeAction()ҎQ将执行他们自己的业务逻辑。因此,在struts-web.xml配置文g中ؓ所有承自Action基类的子cd明个一名ؓsessionEnded的forward元素q将其指向login.jsp是至关重要的。清?以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>

						



l承自BaseActioncȝ子类Secure1Action实现了executeAction()Ҏ而不是覆盖它。Secure1ActioncM需要执行Q何退Z码,如清?Q?br>


清单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" ));
         }
}
						



上面的解x案是如此的优雅有效,它仅仅只需要定义一个基c而不需要额外的代码工作。将通用的行为方法写成一个承StrutsAction的基cL者的推荐的,而且q是许多Struts目的共同经验?br>



十一. 局限?/font>

上述解决Ҏ对JSP或基于Struts的Web应用都是非常单而实用的Q但它还是有某些局限。在我看来,q些局限ƈ不是臛_紧要的?br>
•   通过取消与浏览器后退按钮有关的缓存机Ӟ一旦用L开面而没有对数据q行提交Q那么页面将会丢失所有输入的数据。即使点L览器的后退按钮q回到刚才的面也无于事,因ؓ览器会从服务器获取新的I白面昄出来。一U可能的Ҏq不是阻止这些JSP面包含数据数据表格。在ZJSP的解x案当中,那些JSP面可以删除在清?中的代码。在ZStruts的解x案当中,Actionc需要承自Struts的Actionc而非BaseActioncR?br>
•  上面讲述的方法在Opera览器中不能工作。事实上没有适用于Opera览器的解决ҎQ因为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.

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.

q运的是Q用微软的IE和基于Mozilla的浏览器用户多余Opera览器。上面讲q的解决Ҏ对大多数用户来说q是有帮助的。另外,无论是否使用上述的解x案,Opera览器仍然存在用户退出问题,Opera来说没有M改变。然而,正如RFC2616中所_通过像上面一栯|头文g指oQ当用户点击一个链接时QOpera览器不会从~存中获取页面?br>



十二. l论

q篇文章讲述了处理退出问题的解决ҎQ尽方案简单的令h惊讶Q但在所有情况下都能有效地工作。无论是对JSPq是StrutsQ所要做的不q是写一D不过50行的代码以及一个记录用h后登陆时间的Ҏ。在有密码保护的Web应用中用这些方案能够确保在M情况下用LUh数据不致泄露Q同Ӟ也能增加用户的经验?/font>


]]>
վ֩ģ壺 | | ̩| Ӷ| ʢ| Ĭ| | »| Ԫ| ˮ| | | | ˲| | ɽ| | | ľ| | ƾ| ͡| | ̫| | Դ| | | | ̽| ̨| Ƥɽ| | | ƽ| ̩| ̨| ӳ| «Ϫ| ˫| ˮ|