📄 jsp和struts解决用户退出问题 - fanqiang_com.htm
字号:
Pontarelli的经典文章《J2EE Security: Container Versus
Custom》讨论了不同的J2EE认证途径。文章同时指出,HTTP协议和基于form的认证并未提供处理用户退出的机制。因此,解决途径便是引入自定义的安全实现机制。<BR><BR> 自定义的安全认证机制普遍采用的方法是从form中获得用户输入的认证信息,然后到诸如LDAP
(lightweight directory access
protocol)或关系数据库的安全域中进行认证。如果用户提供的认证信息是有效的,登陆动作往HttpSession对象中注入某个对象。
HttpSession存在着注入的对象则表示用户已经登陆。为了方便读者理解,本文所附的示例只往HttpSession中写入一个用户名以表明用户已经登陆。清单1是从loginAction.jsp页面中节选的一段代码以此阐述登陆动作:<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#d8d8d1
border=1>
<TBODY>
<TR>
<TD>Listing 1 <BR>//...<BR>//initialize RequestDispatcher
object; set forward to home page by
default<BR>RequestDispatcher rd =
request.getRequestDispatcher("home.jsp");<BR><BR>//Prepare
connection and statement<BR>rs = stmt.executeQuery("select
password from USER where userName = '" + userName +
"'");<BR>if (rs.next()) { <BR> //Query only returns 1 record
in the result set; only 1 <BR> password per userName which is
also the primary key<BR> if
(rs.getString("password").equals(password)) { //If valid
password<BR> session.setAttribute("User", userName); //Saves
username string in the session object<BR> }<BR> else {
//Password does not match, i.e., invalid user
password<BR> request.setAttribute("Error", "Invalid
password."); <BR><BR> rd =
request.getRequestDispatcher("login.jsp");<BR> }<BR>} //No
record in the result set, i.e., invalid username<BR>else
{<BR><BR> request.setAttribute("Error", "Invalid user
name.");<BR> rd =
request.getRequestDispatcher("login.jsp");<BR>}<BR>}<BR><BR>//As
a controller, loginAction.jsp finally either forwards to
"login.jsp" or "home.jsp"<BR>rd.forward(request,
response);<BR>//...</TD></TR></TBODY></TABLE><BR> 本文所附示例均以关系型数据库作为安全域,但本文所阐述的观点对任何类型的安全域都是适用的。<BR><BR> Logout
action
<BR><BR> 退出动作就包含了简单的删除用户名以及对用户的HttpSession对象调用invalidate()方法。清单2是从loginoutAction.jsp页面中节选的一段代码以此阐述退出动作:<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#d8d8d1
border=1>
<TBODY>
<TR>
<TD>Listing 2
<BR>//...<BR>session.removeAttribute("User");<BR>session.invalidate();<BR>//...</TD></TR></TBODY></TABLE></SPAN><!--StartFragment --><SPAN
class=f14><B>阻止未经认证访问受保护的JSP页面</B><BR><BR> 从form中获取用户提交的认证信息并经过验证后,登陆动作简单地往
HttpSession对象中写入一个用户名,退出动作则做相反的工作,它从用户的HttpSession对象中删除用户名并调用invalidate
()方法销毁HttpSession。为了使登陆和退出动作真正发挥作用,所有受保护的JSP页面都应该首先验证HttpSession中是否包含了用户名以确认当前用户是否已经登陆。如果HttpSession中包含了用户名,也就是说用户已经登陆,Web应用则将剩余的JSP页发送给浏览器,否则,
JSP页将跳转到登陆页login.jsp。页面home.jsp, secure1.jsp,
secure2.jsp和logout.jsp均包含清单3中的代码段:<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#d8d8d1
border=1>
<TBODY>
<TR>
<TD>Listing 3 <BR>//...<BR>String userName = (String)
session.getAttribute("User");<BR>if (null == userName)
{<BR> request.setAttribute("Error", "Session has ended. Please
login.");<BR> RequestDispatcher rd =
request.getRequestDispatcher("login.jsp");<BR> rd.forward(request,
response);<BR>}<BR>//...<BR>//Allow the rest of the dynamic
content in this JSP to be served to the
browser<BR>//...</TD></TR></TBODY></TABLE><BR> 在这个代码段中,程序从HttpSession中减缩username字符串。如果字符串为空,Web应用则自动中止执行当前页面并跳转到登陆页,同时给出Session
has ended. Please log
in.的提示;如果不为空,Web应用则继续执行,也就是把剩余的页面提供给用户。<BR><BR> 运行logoutSampleJSP1<BR><BR> 运行logoutSampleJSP1将会出现如下几种情形:<BR><BR> 1)
如果用户没有登陆,Web应用将会正确中止受保护页面home.jsp, secure1.jsp,
secure2.jsp和logout.jsp的执行,也就是说,假如用户在浏览器地址栏中直接敲入受保护JSP页的地址试图访问,Web应用将自动跳转到登陆页并提示Session
has ended.Please log in.<BR><BR> 2)
同样的,当一个用户已经退出,Web应用也会正确中止受保护页面home.jsp, secure1.jsp,
secure2.jsp和logout.jsp的执行<BR><BR> 3)
用户退出后,如果点击浏览器上的后退按钮,Web应用将不能正确保护受保护的页面——在Session销毁后(用户退出)受保护的JSP页重新在浏览器中显示出来。然而,如果用户点击返回页面上的任何链接,Web应用将会跳转到登陆页面并提示Session
has ended.Please log
in.<BR><BR> <B>阻止浏览器缓存</B><BR><BR> 上述问题的根源在于大部分浏览器都有一个后退按钮。当点击后退按钮时,默认情况下浏览器不是从Web服务器上重新获取页面,而是从浏览器缓存中载入页面。基于Java的Web应用并未限制这一功能,在基于PHP、ASP和.NET的Web应用中也同样存在这一问题。<BR><BR> 在用户点击后退按钮后,浏览器到服务器再从服务器到浏览器这样通常意思上的HTTP回路并没有建立,仅仅只是用户,浏览器和缓存进行了交互。所以,即使包含了清单3上的代码来保护JSP页面,当点击后退按钮时,这些代码是不会执行的。<BR><BR> 缓存的好坏,真是仁者见仁智者见智。缓存的确提供了一些便利,但通常只在使用静态的HTML页面或基于图形或影响的页面你才能感受到。而另一方面,Web应用通常是基于数据的,数据通常是频繁更改的。与从缓存中读取并显示过期的数据相比,提供最新的数据才是更重要的!<BR><BR> 幸运的是,HTTP头信息“Expires”和“Cache-Control”为应用程序服务器提供了一个控制浏览器和代理服务器上缓存的机制。
HTTP头信息Expires告诉代理服务器它的缓存页面何时将过期。HTTP1.1规范中新定义的头信息Cache-Control可以通知浏览器不缓存任何页面。当点击后退按钮时,浏览器重新访问服务器已获取页面。如下是使用Cache-Control的基本方法:<BR><BR> 1)
no-cache:强制缓存从服务器上获取新的页面<BR><BR> 2) no-store:
在任何环境下缓存不保存任何页面<BR><BR> HTTP1.0规范中的Pragma:no-cache等同于HTTP1.1规范中的Cache-Control:no-cache,同样可以包含在头信息中。
<BR><BR> 通过使用HTTP头信息的cache控制,第二个示例应用logoutSampleJSP2解决了logoutSampleJSP1的问题。
logoutSampleJSP2与logoutSampleJSP1不同表现在如下代码段中,这一代码段加入进所有受保护的页面中:<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#d8d8d1
border=1>
<TBODY>
<TR>
<TD>//...<BR>response.setHeader("Cache-Control","no-cache");
//Forces caches to obtain a new copy of the page from the
origin
server<BR>response.setHeader("Cache-Control","no-store");
//Directs caches not to store the page under any
circumstance<BR>response.setDateHeader("Expires", 0); //Causes
the proxy cache to see the page as
"stale"<BR>response.setHeader("Pragma","no-cache"); //HTTP 1.0
backward compatibility<BR>String userName = (String)
session.getAttribute("User");<BR>if (null == userName)
{<BR> request.setAttribute("Error", "Session has ended. Please
login.");<BR> RequestDispatcher rd =
request.getRequestDispatcher("login.jsp");<BR> rd.forward(request,
response);<BR>}<BR>//...</TD></TR></TBODY></TABLE><BR> 通过设置头信息和检查HttpSession中的用户名确保了浏览器不缓存页面,同时,如果用户未登陆,受保护的JSP页面将不会发送到浏览器,取而代之的将是登陆页面login.jsp。<BR><BR> <B>运行logoutSampleJSP2</B><BR><BR> 运行logoutSampleJSP2后将回看到如下结果:<BR><BR> 1)
当用户退出后试图点击后退按钮,浏览器并不会显示受保护的页面,它只会现实登陆页login.jsp同时给出提示信息Session has
ended. Please log in.<BR><BR> 2)
然而,当按了后退按钮返回的页是处理用户提交数据的页面时,IE和Avant浏览器将弹出如下信息提示:<BR><BR> 警告:页面已过期……(你肯定见过)<BR><BR> 选择刷新后前一个JSP页面将重新显示在浏览器中。很显然,这不是我们所想看到的因为它违背了logout动作的目的。发生这一现象时,很可能是一个恶意用户在尝试获取其他用户的数据。然而,这个问题仅仅出现在后退按钮对应的是一个处理POST请求的页面。<BR><BR> <B>记录最后登陆时间</B>
<BR><BR> 上述问题之所以出现是因为浏览器将其缓存中的数据重新提交了。这本文的例子中,数据包含了用户名和密码。无论是否给出安全警告信息,浏览器此时起到了负面作用。<BR><BR> 为了解决logoutSampleJSP2中出现的问题,logoutSampleJSP3的login.jsp在包含username和
password的基础上还包含了一个称作lastLogon的隐藏表单域,此表单域动态的用一个long型值初始化。这个long型值是调用
System.currentTimeMillis()获取到的自1970年1月1日以来的毫秒数。当login.jsp中的form提交时,
loginAction.jsp首先将隐藏域中的值与用户数据库中的值进行比较。只有当lastLogon表单域中的值大于数据库中的值时Web应用才认为这是个有效的登陆。<BR><BR> 为了验证登陆,数据库中lastLogon字段必须以表单中的lastLogon值进行更新。上例中,当浏览器重复提交数据时,表单中的lastLogon值不比数据库中的lastLogon值大,因此,loginAction转到login.jsp页面,并提示
Session has ended.Please log
in.清单5是loginAction中节选的代码段:<BR><BR> 清单5<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#d8d8d1
border=1>
<TBODY>
<TR>
<TD>//...<BR>RequestDispatcher rd =
request.getRequestDispatcher("home.jsp"); //Forward to
homepage by default<BR>//...<BR>if
(rs.getString("password").equals(password)) { <BR> //If valid
password<BR> long lastLogonDB =
rs.getLong("lastLogon");<BR> if (lastLogonForm > lastLogonDB)
{<BR> session.setAttribute("User", userName); //Saves
username string in the session
object<BR> stmt.executeUpdate("update USER set lastLogon= " +
lastLogonForm + " where userName = '" + userName +
"'");<BR> }<BR> else {<BR> request.setAttribute("Error",
"Session has ended. Please login.");<BR> rd =
request.getRequestDispatcher("login.jsp"); }<BR> }<BR> else {
//Password does not match, i.e., invalid user
password<BR> request.setAttribute("Error", "Invalid
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -