Session和Cookie的深入研究

Session是一种服务器端技术,Session对象在服务器端创建,通常采用散列表来存储信息,例如,Tomcat的Session实现采用HashMap对象来存储属性名和属性值。
Cookie是由Netscape公司发明的、用于跟踪用户会话的一种方式。Cookie是由服务器发送给客户的片段信息,存储在客户端浏览器的内存中或硬盘上,在客户随后对该服务器的请求中发回它。
Session与Cookie的最大的区别是,Session在服务端保存信息,Cookie在客户端保存信息。为了跟踪用户的会话,服务器端在创建Session后,需要将Session ID交给客户端,在客户端下次请求时,将这个ID随请求一起发送回来。可以采用Cookie或URL重写的方式,将Session ID发送给客户端。
在5.2.2节的基于Cookie的会话跟踪的例子中,当访问了Servlet后,关闭浏览器,再次打开浏览器访问Servlet时,可以看到开始了一次新的会话。如果我们同时打开两个浏览器,访问同一个URL,那么每一个浏览器进程都将开始一个新的会话,如图5-8所示。而在“使用Cookie的实例”中,当我登录后,关闭浏览器,再打开浏览器,访问GreetServlet2时,直接出现了欢迎页面。然后我们同时打开两个浏览器,访问GreetServlet2时,也直接出现了欢迎页面。也就说,在5.2.2节的例子中,保存Session ID的Cookie在关闭浏览器后就删除了,不能在多个浏览器进程间共享。而在“使用Cookie的实例”的例子中,保存用户名和密码的Cookie在浏览器关闭后,再次打开,仍然存在,可以在多个浏览器进程间共享。
通常,我们将用于会话跟踪的Cookie叫做会话Cookie,Servlet规范中,用于会话跟踪的Cookie的名字必须是JSESSIONID,它通常保存在浏览器的内存中。在浏览器内存中的会话Cookie不能被不同的浏览器进程所共享。在网络上,很多人有一种错误的认识,认为以不同的方式打开浏览器窗口,或者使用其他的非IE浏览器就可以在不同的浏览器进程之间共享会话Cookie下面,我们和读者一起看看这种错误认识是如何产生的。
首先看看IE浏览器,启动Tomcat服务器,打开IE浏览器,访问5.2.2节中的例子程序,在地址栏中输入:http://localhost:8080/ch05/login,然后单击IE浏览器菜单栏上的“文件”→“新建”→“窗口”(也可以在浏览器窗口上按下Ctrl+N组合键)打开一个新窗口,分别刷新原窗口和新窗口,可以看到两个窗口中显示的Session ID是同一个。由实验可以看到,某些方式下打开的多个IE浏览器,可以共享内存中的Cookie,而另外一些方式打开的多个IE浏览器,则不能共享内存中的Cookie。再使用其他浏览器进行验证,发现有的浏览器(例如Mozilla FireFox)不管以什么方式打开窗口,都可以共享内存中的Cookie。由此,一个错误的结论就产生了:不同的浏览器对内存中的Cookie有不同的处理方式,有的浏览器(FireFox)可以在多个浏览器进程间共享会话Cookie,IE浏览器是否共享Cookie,要看浏览器打开的方式。

笔者为什么会说上述结论是错误的呢?这是因为得出结论的人不了解进程的概念。读者在以上述方式打开两个IE浏览器窗口后(如果有其他的IE浏览器打开,请先关闭),同时按下Ctrl+Alt+Del组合键,选择“任务管理器”,切换到“进程”标签页,在“映像名称”下查找IEXPLORE.EXE,看看找到了几个IEXPLORE.EXE,是不是只有一个!那么这说明了什么?说明我们所看到的两个IE浏览器窗口实际上是属于同一个IE浏览器进程,难怪它们能共享内存中的会话Cookie。以其他方式打开一个IE浏览器,可以看到在“映像名称”下多了一个IEXPLORE.EXE进程,既然是两个IE浏览器进程的窗口,当然无法共享内存中的Cookie了。同样的道理,Mozilla FireFox不管以什么方式打开窗口,始终只启动了一个进程,既然这些窗口同属于一个进程,当然可以共享内存中的Cookie了。所以我们说,对于存储在内存中的Cookie,是不能被不同的浏览器进程所共享的。共享只能发生在同一个浏览器进程的不同窗口中(因为这些窗口共享同一个进程地址空间)。关于进程和线程的知识,读者可以参看相关的书籍。

对于保存在硬盘上的Cookie,因为是在外部的存储设备中存储,所以可以在多个浏览器进程间共享。
有些人对Session的使用存在着一种误解,认为浏览器一旦关闭,Session就消失了。这是因为有的人看到,关闭浏览器后,再打开一个浏览器,就开始了一次新的会话,从而得出了结论。再回头看看第5.1节中的顾客在超市存包的例子,顾客存好包,购物完毕,忘了取包就走了,但存包处的管理员不知道顾客已经走了,所以他必须继续用柜子(相当于Session)存放顾客的物品,直到长时间没有人来取(Session的超时值发生),管理员才清除柜子。之所以会有“浏览器一旦关闭,Session就消失了”这种错误的认识,主要是因为保存Session ID的Cookie是存储在浏览器的内存中,一旦浏览器关闭,Cookie将被删除,Session ID也就丢失了。当再次打开浏览器连接服务器时,服务器没有收到Session ID,当然也就无法找到先前的Session,于是服务器就创建了一个新的Session。而这个时候先前的Session是仍然存在的,直到设置的Session超时时间间隔发生,Session才被服务器清除。如果我们将会话Cookie保存到硬盘上,或者通过某种技术手段改写浏览器向服务器发送的请求报头,将原先的Session ID发送给服务器,则再次打开的浏览器就能够找到原来的Session了。

我们想了解一下,用于会话跟踪的Cookie是如何创建的,为何只能保存在浏览器的内存中,而不能保存到用户的硬盘上。在Tomcat 6.0.16.中,Session的创建是调用org.apache.catalina.connector.Request类中的doGetSession()方法来完成的。下面我们给出这个方法的代码片段:

Java代码

1.protected Session doGetSession(boolean create) 
2.{
3.    …
4.    // Creating a new session cookie based on that session
5.    if ((session != null) && (getContext() != null)
6.           && getContext().getCookies()) 
7.    {
8.        Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
9.                                           session.getIdInternal());
10.        configureSessionCookie(cookie);
11.        response.addCookieInternal(cookie);
12.    }
13.
14.    if (session != null) 
15.    {
16.        session.access();
17.        return (session);
18.    }
19.    else
20.    {
21.        return (null);
22.    }
23.}
24.
25.protected void configureSessionCookie(Cookie cookie) 
26.{
27.    cookie.setMaxAge(-1);
28.    String contextPath = null;
29.    if (!connector.getEmptySessionPath() && (getContext() != null)) 
30.    {
31.        contextPath = getContext().getEncodedPath();
32.    }
33.    if ((contextPath != null) && (contextPath.length() > 0)) 
34.    {
35.        cookie.setPath(contextPath);
36.    }
37.    else 
38.    {
39.        cookie.setPath("/");
40.    }
41.    if (isSecure()) 
42.    {
43.        cookie.setSecure(true);
44.    }
45.}

 

代码的第8行,我们看到非常熟悉的创建Cookie对象的代码,Cookie的名字是Globals.SESSION_ COOKIE_NAME,SESSION_COOKIE_NAME被定义为静态的常量,其值为JSESSIONID。Cookie的值是调用session.getIdInternal ()得到的Session ID。第10行,调用了configureSessionCookie()方法来配置会话Cookie。我们转到configureSessionCookie()方法中,第27行,调用Cookie对象的setMaxAge()方法设置Cookie的生存时间,在“使用Cookie的实例”的例子中,我们说过,如果时间值为负数,那么当客户端的浏览器退出,Cookie将会被删除。看到这儿,我们就知道了为什么会话Cookie只能保存在内存中了,这是由Tomcat的实现决定的。第35行,调用Cookie对象的setPath()方法,指定这个Cookie在当前Web应用程序的上下文路径下有效。


发表评论

电子邮件地址不会被公开。 必填项已用*标注

28 − = 21