• 当用户退出后试图点击后退按钮,浏览器不会重新显示受保护的页面,它只会显示登陆页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 divcaution, 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
|
为了实现上述方法,你必须记录每个用户的最后登陆时间。对于采用关系型数据库安全域来说,这点可以可以通过在某个表中加上lastLogin字段轻松实现。虽然对LDAP以及其他的安全域来说需要稍微动下脑筋,但最后登陆方法很显然是可以实现的。
表示最后登陆时间的方法有很多。示例logoutSampleJSP3利用了自1970年1月1日以来的毫秒数。这个方法即使在许多人在不同浏览器中用一个用户账号登陆时也是可行的。
九. 运行logoutSampleJSP3
运行示例logoutSampleJSP3将展示如何正确处理退出问题。一旦用户退出,点击浏览器上的后退按钮在任何情况下都不会在浏览器中显示受保护的JSP页面。这个示例展示了如何正确处理退出问题而不需要对用户进行额外的培训。
为了使代码更简练有效,一些冗余的代码可以剔除。一种途径就是把清单4中的代码写到一个单独的JSP页中,其他JSP页面可以通过标签
十. 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
|

