Session和Cookie的理解辨析与小试牛刀
Cookie机制采用的是在客户端保持状态的方案。cookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。
Session机制采用的是一种在客户端与服务器之间保持状态的解决方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的。
session是针对每一个用户的,变量的值保存在服务器上,用一个sessionID来区分是哪个用户session变量, 这个sessionID就是我们常说的JSESSIONID,这个值是通过用户的浏览器在访问的时候返回给服务器,当客户禁用cookie时,这个值可能通过其他方法传送回服务器。
方法1) 重写URL
URL重写是利用GET的方法,在URL的尾部添加一些额外的参数来达到会话追踪(session tracking)的目的,服务器将这个标识符与它所存储的有关会话的数据关联起来。URL看起来如下:
http://host/path/file.html;jsessionid=1234
方法2) 隐藏表单字段
隐藏表单字段的方法,是利用HTML内hidden的属性,把客户端的信息,在用户不察觉的情形下,偷偷地随着请求一起传送给到服务器处理,这样一来,就可以进行会话跟踪的任务了。可以下列的方法来做隐藏表单字段的会话追踪。
<input type=”hidden” name=”userID” value=”15123″>
http是无状态的协议,客户每次读取web页面时,服务器都打开新的会话(从网络服务器观点看所有HTTP请求都独立于先前请求。就是说每一个HTTP响应完全依赖于相应请求中包含的信息),而且服务器也不会自动维护客户的上下文信息,那么要怎么才能实现网上商店中的购物车呢,session就是一种保存上下文信息的机制,它是针对每一个用户的,变量的值保存在服务器端,通过SessionID来区分不同的客户,session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,我们叫做session cookie,以区别persistent cookies,也就是我们通常所说的cookie,注意session cookie是存储于浏览器内存中的,并不是写到硬盘上的,这也就是我们刚才看到的JSESSIONID,我们通常情是看不到JSESSIONID的,但是当我们把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,我们就可以在地址栏看到sessionid=KWJHUG6JJM65HS2K6之类的字符串。
明白了原理,我们就可以很容易的分辨出persistent cookies和session cookie的区别了,网上那些关于两者安全性的讨论也就一目了然了,session cookie针对某一次会话而言,会话结束session cookie也就随着消失了,而persistent cookie只是存在于客户端硬盘上的一段文本(通常是加密的),而且可能会遭到cookie欺骗以及针对cookie的跨站脚本攻击,自然不如session cookie安全了。
通常session cookie是不能跨窗口使用的,当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的sessionid,这样我们信息共享的目的就达不到了,此时我们可以先把sessionid保存在persistent cookie中,然后在新窗口中读出来,就可以得到上一个窗口SessionID了,这样通过session cookie和persistent cookie的结合我们就实现了跨窗口的session tracking(会话跟踪)。
正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。如下图Firebug截图所示:
Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。如下图Firebug截图所示。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存在内存中(session cookie)或者保存在一个本地文件(persistence cookie),它会自动将同一服务器的任何请求缚上这些cookies。
从网络服务器观点看所有HTTP请求都独立于先前请求。就是说每一个HTTP响应完全依赖于相应请求中包含的信息。在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个 session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。
针对Cookie与Session的实现,我做了一下试验。
模仿现今SNS网站登录策略:
先决条件:浏览器支持并且没有禁用Cookie
CASE:
1. 第一次登录,不记住密码 -> 关闭浏览器,再次打开浏览器打开网页 -> 需要登录
2. 第一次登录,记住密码 -> 关闭浏览器,再次打开浏览器打开网页 -> 不需要登录
3. 第一次登录,不记住密码/记住密码 -> 刷新页面 -> 不需要登录
4. 老用户登录,不记住密码 -> 关闭浏览器,再次打开浏览器打开网页 -> 需要登录
5. 老用户登录,记住密码 -> 关闭浏览器,再次打开浏览器打开网页 -> 不需要登录
6. 老用户登录,不记住密码/记住密码 -> 刷新页面 -> 不需要登录
7. 退出登录 -> 刷新页面/关闭浏览器 -> 重新登录
<%@ page contentType="text/html;charset=GB2312" %> <html> <head> <title>index</title> </head> <body> <% //初始化,用于保存Cookie中的用户名、密码、JSESSIONID String _username = ""; String _password = ""; String _jsessionid = ""; //获取全部Cookie Cookie c[] = request.getCookies(); if (c != null) { for(int i = 0; i < c.length; i++) { //在Cookie中查找用户名、密码,如果找到,则分别将其赋值给用户名、密码、JSESSIONID变量 if("username".equals(c[i].getName())) _username = c[i].getValue(); if("password".equals(c[i].getName())) _password = c[i].getValue(); if("JSESSIONID".equals(c[i].getName())) _jsessionid = c[i].getValue(); } } // 如果检测到服务器上存在session并且该session有对应的userid,则认为此用户有可能已经登录,但还需要验证JSESSIONID才能确实是否无须再次登录 String jsessionid = session.getId(); if (jsessionid != null && session.getAttribute("userid") != null) { if (jsessionid.equals(_jsessionid)) { response.sendRedirect("pass.jsp"); return; } } if(!"".equals(_username) && !"".equals(_password)) { //Cookie中有用户名、密码,将用户名、密码提交到验证页面 response.sendRedirect("check.jsp?username="+_username+"&password="+_password); } else { //Cookie中没有用户名、密码,跳转到登录页面 %> <jsp:forward page="login.jsp" /> <% } %> </body> </html> |
login.jsp:
<%@ page contentType="text/html;charset=GB2312" %> <html> <head> <title>登录</title> </head> <body> <center> <h1>登录页面</h1> <hr> <form action="check.jsp" method="post"> <table> <tr> <td>用户名:</td> <td><input type="text" name="username" /></td> </tr> <tr> <td>密 码:</td> <td><input type="password" name="password" /></td> </tr> <tr> <td>Cookie选项:</td> <td> <input type="radio" name="cookie" value="nosave" checked>不保存 <input type="radio" name="cookie" value="save">保存1分钟 </td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" value="登录" /> <input type="reset" value="重置" /> </td> </tr> </table> </form> </center> |
check.jsp:
<%@ page contentType="text/html;charset=GB2312" %> <html> <head> <title>验证页面</title> </head> <body> <% String Username=request.getParameter("username"); String Password=request.getParameter("password"); String IsCookie=request.getParameter("cookie"); //判断用户名、密码的合法性 if("neo".equals(Username) && "123".equals(Password)) //为了避免空指向异常,比较两个字符串时,如有字符串常量,则使用字符串常量的“equals”方法(即将常量写在前面)。 { //合法用户 if("save".equals(IsCookie)) { //如果选择了保存Cookie选项,则保存Cookie Cookie c1=new Cookie("username",Username); Cookie c2=new Cookie("password",Password); //设置Cookie保存时间为1分钟 c1.setMaxAge(60); c2.setMaxAge(60); response.addCookie(c1); response.addCookie(c2); } session.setAttribute("userid",Username); response.sendRedirect("pass.jsp"); //跳转到欢迎页面 } else { //非法用户,内部重定向到登录失败页面,此时URL不变 %> <jsp:forward page="failure.jsp" /> <% } %> </body> </html> </body> </html> |
pass.jsp:
<%@page contentType="text/html;charset=GB2312" %> <center> <h1>登录成功!!</h1> <hr> <h3>欢迎<font size="12" color="red"> <%--forward跳转为服务器端跳转,跳转后仍在check.jsp页面,可以继续使用usename参数 --%> <%=session.getAttribute("userid") %> </font>光临!</h3> 您的 session id 为 <%=request.getSession().getId() %> <a href="logoff.jsp">退出</a> </center> |
failure.jsp:
<%@ page contentType="text/html;charset=GB2312" %> <div align="center"> <h1>登录失败!!</h1> <hr> <a href="login.jsp">重新登录</a> </div> |
logoff.jsp:
<%@ page contentType="text/html;charset=GB2312" %> <html> <head> <title>登录</title> </head> <body> <% request.getSession().removeAttribute("userid"); request.getSession().invalidate(); Cookie c1=new Cookie("username",null); Cookie c2=new Cookie("password",null); response.addCookie(c1); response.addCookie(c2); response.sendRedirect("index.jsp"); %> </body> </html> |