`
luvi
  • 浏览: 36828 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Tomcat之Session和Cookie大揭密

阅读更多
关键字: tomcat之session和cookie大揭密
转载自
http://tech.it168.com/j/2007-09-29/200709291005796.shtml
作者:IT168 极地圣火  2007-09-29


一、JSP和Servlet中的Cookie

    由于HTTP协议是无状态协议(虽然Socket连接是有状态的,但每次用HTTP协议进行数据传输后就关闭的Socket连接,因此,HTTP协议并不会保存上一次的状态),因此,如果要保存某些HTTP请求过程中所产生的数据,就必须要有一种类似全局变量的机制保证数据在不同的HTTP请求之间共享。这就是下面要讲的Session和Cookie。

    Cookie是通过将数据保存在客户端的硬盘(永久Cookie)或内存(临时Cookie)中来实现数据共享的一种机制。在Windows下,保存在这些Cookie数据的目录一般是C:\Documents and Settings\Administrator\Cookies。每一个Cookie有一个超时时间,如果超过了这个时间,Cookie将自动失效。可按如下方法来设置Cookie的超时时间:
Java代码 复制代码
  1. Cookie cookie = new Cookie("key","value");   
  2. cookie.setMaxAge(3600);  // Cookie的超时间为3600秒,也就是1小时   
  3. response.addCookie(cookie);  

如果不使用setMaxAge方法,Cookie的超时时间为-1,在这种情况下,Cookie就是临时Cookie,也就是说这种Cookie实际上并不保存在客户端硬盘上,而是保存在客户端内存中的。读者可以在JSP中运行如下代码,看看是否会在上面提到的保存cookie的目录中生成cookie文件:
Java代码 复制代码
  1. Cookie cookie = new Cookie("key","value");   
  2. esponse.addCookie(cookie);  


实际上使用setMaxAge将超时时间设为任意的负数都会被客户端浏览器认为是临时
Cookie,如下面的代码将在客户端内存中保存一个临时Cookie:

Java代码 复制代码
  1. Cookie cookie = new Cookie("key","value");   
  2. cookie.setMaxAge(-100);  // 将cookie设为临时Cookie   
  3. response.addCookie(cookie);  


如果第一次将Cookie写入客户端(不管是硬盘还是内存),在同一台机器上第二次访问
该网站的jsp页面时,会自动将客户端的cookie作为HTTP请求头的Cookie字段值传给服务端,如果有多个Cookie,中间用";"隔开。如下面的HTTP请求头所示:
Java代码 复制代码
  1. GET /test/First.jsp HTTP/1.1  
  2. HOST:localhost   
  3. ...   
  4. Cookie:key1=value1;key2=value2   
  5. ...   
  6. ...  

我们可以在JSP中使用如下的Java代码来输出Cookie字段的值:
Java代码 复制代码
  1. out.println(request.getHeader("Cookie"));  

如果在Servlet中输出,必须得使用如下语句得到out,才能向客户端浏览器输出数据:
Java代码 复制代码
  1. PrintWriter out = response.getWriter();  

    虽然永久Cookie和临时Cookie在第二次向服务端发出HTTP请求时生成Cookie字段,但它们还是有一定的区别的。永久Cookie在任意新开启的IE窗口都可以生成Cookie。而临时Cookie由于只保存在当前IE窗口,因此,在新开启的IE窗口,是不能生成Cookie字段的,也就是说,新窗口和旧窗口是不能共享临时Cookie的。使用重定向机制弹出的新窗口也无法和旧窗口共享临时Cookie。但在同一个窗口可以。如在一个IE窗口输入http://localhost:8080/test/first.jsp,向内存写入一个临时Cookie后,在同一个IE窗口输入http://localhost:8080/test/second.jsp,浏览器在向服务端发送HTTP请求时,自动将当前浏览器的临时Cookie(也就是first.jsp所创建的Cookie)和永久Cookie作为HTTP请求头的Cookie字段值发送给服务端。但是如果新启一个IE窗口,由于新IE窗口没有这个临时Cookie,因此,second.jsp只发送了保存在硬盘上的永久Cookie。
二、Tomcat中的Servlet和Session

    由于Cookie数存在保存在客户端,这样对于一些敏感数据会带来一些风险。而且Cookie一般只能保存字符串等简单数据。并且大小限制在4KB。如果要保存比较复杂的数据,Cookie可能显得有些不合适。基于这些原因,我们自然会想到在服务端采用这种类似Cookie的机制来存储数据。这就是我们这节要讲的会话(Session)。而在一个客户端和服务端的会话中所有的页面可以共享为这个会话所建立的Session。

    那么什么是会话呢?有很多人认为会话就是在一台机器上客户端浏览器访问某个域名所指向的服务端程序,就建立了一个客户端到服务端的会话。然后关闭客户端浏览器,会话就结束。其实这并不准确。

    首先让我们先来看看Session的原理。Session和Cookie类似。所不同的是它是建立在服务端的对象。每一个Session对象一个会话。也许很多读者看到这会有一个疑问。Session是如何同客户端联系在一起的呢?很多人在使用Session时并没有感觉到这一点。其实这一切都是Web服务器,如Tomcat一手包办的。那么Web服务器又是如何识别通过HTTP协议进行连接的客户端的呢?这就要用到第一节中所讲的Cookie。在一般情况下,Session使用了临时Cookie来识别某一个Session是否属于某一个会话。在本文中以Tomcat为例来说明Session是如何工作的。

    让我们先假设某一个客户端第一次访问一个Servlet,在这个Servlet中使用了getSession来得到一个Session对象,也就是建立了一个会话,这个Servlet的代码如下:

Java代码 复制代码
  1. import java.io.*;   
  2. import javax.servlet.ServletException;   
  3. import javax.servlet.http.*;   
  4. public class First extends HttpServlet   
  5. {   
  6.   public void doGet(HttpServletRequest request, HttpServletResponse response)   
  7. throws ServletException, IOException   
  8.   {   
  9.      response.setContentType("text/html");   
  10.      HttpSession session = request.getSession();   
  11.      session.setAttribute("key""mySessionValue");   
  12.      PrintWriter out = response.getWriter();   
  13.      out.println("The session has been generated!");   
  14.      out.flush();   
  15.      out.close();   
  16.   }   
  17. }  

对于服务端的First来说,getSession方法主要做了两件事:
    1. 从客户端的HTTP请求头的Cookie字段中获得一个寻找一个JSESSIONID的key,这个key的值是一个唯一字符串,类似于D5A5C79F3C8E8653BC8B4F0860BFDBCD 。
   
2. 如果Cookie中包含这个JSESSIONID,将key的值取出,在Tomcat的Session Map(用于保存Tomcat自启动以来的所有创建的Session)中查找,如果找到,将这个Session取出,如果未找到,创建一个HttpSession对象,并保存在Session Map中,以便下一次使用这个Key来获得这个Session。

在服务器向客户端发送响应信息时,如果是新创建的HttpSession对象,在响应HTTP
头中加了一个Set-Cookie字段,并将JSESSIONID和相应的值反回给客户端。如下面的HTTP响应头:
Java代码 复制代码
  1. HTTP/1.1 200 OK   
  2. ...   
  3. Set-Cookie: JSESSIONID=D5A5C79F3C8E8653BC8B4F0860BFDBCD   
  4. ...  


    对于客户端浏览器来说,并不认识哪个Cookie是用于Session的,它只是将相应的临时Cookie和永久Cookie原封不动地放到请求HTTP头的Cookie字段中,发送给服务器。如果在IE中首次访问服务端的First,这时在当前IE窗口并没有临时Cookie,因此,在请求HTTP头中就没有Cookie字段,所以First在调用getSession方法时就未找到JSESSIONID,因此,就会新建一个HttpSession对象。并在Set-Cookie中将这个JSESSIONID返回。接下来我们使用另外一个Servlet:Second来获得在First中所设置的Session数据。Second的代码如下:
Java代码 复制代码
  1. import java.io.*;   
  2. import javax.servlet.ServletException;   
  3. import javax.servlet.http.*;   
  4. public class Second extends HttpServlet   
  5. {   
  6.     public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException   
  7.     {   
  8.         response.setContentType("text/html");   
  9.         HttpSession session = request.getSession();   
  10.         PrintWriter out = response.getWriter();   
  11.         out.println(session.getAttribute("key"));   
  12.         out.flush();   
  13.         out.close();   
  14.     }   
  15. }  


     如果在同一个窗口来调用Second。这时客户端已经有了一个临时Cookie,就是JSESSIONID,因此,会将这个Cookie放到HTTP头的Cookie字段中发送给服务端。服务端在收到这个HTTP请求时就可以从Cookie中得到JSESSIONID的值,并从Session Map中找到这个Session对象,也就是getSession方法的返回值。因此,从技术层面上来说,所有拥有同一个Session ID的页面都应该属于同一个会话。

    如果我们在一个新的IE窗口调用Second,并不会得到mySessionValue。因为这时Second和First拥有了不同的Session ID,因此,它们并不属于同一个会话。讲到这,也许很多读者眼前一亮。既然拥有同一个Session ID,就可以共享Session对象,那么我们可不可以使用永久Cookie将这个Session ID保存在Cookie文件中,这样就算在新的IE窗口,也可以共享Session对象了。答案是肯定的。下面是新的First代码:

Java代码 复制代码
  1. import java.io.*;   
  2. import javax.servlet.ServletException;   
  3. import javax.servlet.http.*;   
  4. public class First extends HttpServlet   
  5. {   
  6.     public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException   
  7.     {   
  8.         response.setContentType("text/html");   
  9.         HttpSession session = request.getSession();   
  10.         session.setMaxInactiveInterval(3600);   
  11.         Cookie cookie = new Cookie("JSESSIONID", session.getId());   
  12.         cookie.setMaxAge(3600);   
  13.         response.addCookie(cookie);   
  14.         session.setAttribute("key""mySessionValue");   
  15.         PrintWriter out = response.getWriter();   
  16.         out.println("The session has been generated!");   
  17.         out.flush();   
  18.         out.close();   
  19.      }   
  20. }  

    在上面的代码中使用了Cookie对象将JSESSIONID写入了Cookie文件,并使用setMaxAge方法将Cookie超时时间设为3600秒(1小时)。这样只要访问过First,从访问时间算起,在1小时之内,在本机的任何IE窗口调用Second都会得到"mySessionValue"字符串。

三、Tomcat中的JSP和Session

从本质上讲,JSP在运行时已经被编译成相应的Servlet了,因此,在JSP和Servlet中Session的使用方法应该差不多。但还是有一些细小的差别。

如果我们使用过JSP就会发现,在JSP中很多对象是不需要创建的,如out、session等。它们可以直接使用。如下面的JSP代码所示
<!-- MyJSP.jsp -->

Java代码 复制代码
  1. <%@ page language="java" contentType="text/html; charset=GB18030"  
  2.     pageEncoding="GB18030"%>   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">   
  4. <html>   
  5.     <head>   
  6.         <meta http-equiv="Content-Type" content="text/html; charset=GB18030">   
  7.         <title>Insert title here</title>   
  8.     </head>   
  9.     <body>   
  10.         <%   
  11. out.println(session.getId());   
  12. %>   
  13.     </body>   
  14. </html>  


在上面的JSP代码中直接使用了out和session。而并不象Servlet里一样用get方法来获得相应的对象实例。那么这是为什么呢?

由于JSP在第一次运行时是被编译成Servlet的,我们自然就会想到有可能是在编译JSP时自动创建了session和out对象。下面我们就来验证这一点。首先需要查看一下JSP被编译成Servlet后的源代码。这非常简单,如果我们使用的是Tomcat,只需要在Tomcat的安装目录中的work中找相应的源程序即可。如一个名为MyJSP.jsp的文件首先被编译成MyJSP_jsp.java(这就是由JSP生成的Servlet源程序文件),然后再由java将MyJSP_jsp.java编译成MyJSP_jsp.class,最后Tomcat运行的就是MyJSP_jsp.class。如上面的JSP程序被编译成Servlet的部分源代码如下:

Java代码 复制代码
  1. package org.apache.jsp;   
  2. import javax.servlet.*;   
  3. import javax.servlet.http.*;   
  4. import javax.servlet.jsp.*;   
  5. public final class MyJSP_jsp extends org.apache.jasper.runtime.HttpJspBase   
  6. {   
  7. ... ...   
  8. ... ...   
  9. public void _jspService(HttpServletRequest request, HttpServletResponse response)   
  10. throws java.io.IOException, ServletException {   
  11. PageContext pageContext = null;   
  12. HttpSession session = null;   
  13. ServletContext application = null;   
  14. ServletConfig config = null;   
  15. JspWriter out = null;   
  16. Object page = this;   
  17. JspWriter _jspx_out = null;   
  18. PageContext _jspx_page_context = null;   
  19. try {   
  20. response.setContentType("text/html; charset=GB18030");   
  21. pageContext = _jspxFactory.getPageContext(this, request, response,   
  22. nulltrue8192true);   
  23. _jspx_page_context = pageContext;   
  24. application = pageContext.getServletContext();   
  25. config = pageContext.getServletConfig();   
  26. session = pageContext.getSession();   
  27. out = pageContext.getOut();   
  28. _jspx_out = out;   
  29. out.write("\r\n");   
  30. out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n");   
  31. out.write("<html>\r\n");   
  32. out.write("\t<head>\r\n");   
  33. out.write("\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=GB18030\">\r\n");   
  34. out.write("\t\t<title>Insert title here</title>\r\n");   
  35. out.write("\t</head>\r\n");   
  36. out.write("\t<body>\r\n");   
  37. out.write("\t\t");   
  38. out.println(session.getId());   
  39. out.write("\r\n");   
  40. out.write("\r\n");   
  41. out.write("\t</body>\r\n");   
  42. out.write("</html>");   
  43. catch (Throwable t) {   
  44. if (!(t instanceof SkipPageException)){   
  45. out = _jspx_out;   
  46. if (out != null && out.getBufferSize() != 0)   
  47. try { out.clearBuffer(); } catch (java.io.IOException e) {}   
  48. if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);   
  49. }   
  50. finally {   
  51. _jspxFactory.releasePageContext(_jspx_page_context);   
  52. }   
  53. }   
  54. }  


我们可以看到上面的代码中的_jspService方法类似于HttpServlet中的service方法,在方法的开始部分首先建立了session、application、out等对象实例。然后将MyJSP.jsp中的HTML通过out输出到客户端。我们要注意上面的黑体字的语句:out.println(session.getId());,JSP编译器自动将JSP中的<% ... %>中包含的Java代码原封不动地插入到_jspService中。由于是在创建对象实例后插入,因此,就可以直接使用session、out等对象了。

如果我们想做进一步的实验,可以直接使用javac来编译MyJSP_jsp.java,为了方便其间,首先建立一个c.cmd文件,它的内容如下:

javac -classpath
D:\tools\apache-tomcat-6.0.13\lib\servlet-api.jar;D:\tools\apache-tomcat-6.0.13\lib\jsp-api.jar;D:\tools\apache-tomcat-6.0.13\lib\annotations-api.jar;D:\tools\apache-tomcat-6.0.13\lib\catalina.jar;D:\tools\apache-tomcat-6.0.13\lib\jasper.jar;D:\tools\apache-tomcat-6.0.13\lib\el-api.jar %1

其中D:\tools\apache-tomcat-6.0.13是tomcat的安装目录,读者可以将其设为自己的机器上的tomcat安装目录

在编译时可直接使用c MyJSP_jsp.java进行编译,这时tomcat就直接运行我们编译生成的MyJSP_jsp.class了。

从上面的代码我们还可以了解一点,在JSP无论使用还是不使用session,都会使用getSession方法创建一个Session对象,而Servlet必须显式地调用才会建立Session对象。

注:通过直接编译java文件运行jsp,需要清除一下tomcat的缓存,一般需要重启一下tomcat。

 

四、随心所欲使用Session

(1) 使用url传递session id

    在上面讲过,在默认情况下session是依靠客户端的cookie来实现的。但如果客户端浏览器不支持cookie或将cookie功能关闭,那就就意味着无法通过cookie来实现session了。在这种情况下,我们还可以有另一种选择,就是通过url来传递session id。

    对于Tomcat来说,需要使用jsessionid作为key来传递session id。但具体如何传呢?可能有很多人认为会是如下的格式:

http://localhost:8080/test/MyJSP.jsp?jsessionid= D5A5C79F3C8E8653BC8B4F0860BFDBCD

    但实验上面的url并不好使。其实最直接的方法我们可以看一下Tomcat的源程序是如何写的,首先下载tomcat的源程序,然后找到CoyoteAdapter.java文件,并打开。在其中找到parseSessionId方法,这个方法是用来从url中提取Session id的。我们可以不必了解这个方法的全部代码,只看一下开头就可以。代码片段如下:

   

Java代码 复制代码
  1. ByteChunk uriBC = req.requestURI().getByteChunk();   
  2.     int semicolon = uriBC.indexOf(match, 0, match.length(), 0);   
  3.     if (semicolon > 0) {...}  



    上面代码中的uriBC就是请求的url,第二行在这个url中查找match字符串,再在CoyoteAdapter.java中查找一个match字符串,match变量的初值如下:

   

Java代码 复制代码
  1. private static final String match = ";" + Globals.SESSION_PARAMETER_NAME + "=";  



    从上面代码可以看出,match开头是一个";"字符,而SESSION_PARAMETER_NAME是一个常量,值就是"jsessionid",因此可以断定,MyJSP.jsp后跟的是";",并不是"?",因此,正确的url如下:

http://localhost:8080/test/MyJSP.jsp;jsessionid= D5A5C79F3C8E8653BC8B4F0860BFDBCD

    通过使用上述方法甚至可以在不同的机器上获得同一个session对象。

    在CoyoteAdapter.java文件中还有一个parseSessionCookiesId方法,这个方法将从HTTP请求头中提取session id。我们中postParseRequest方法中可以看到将调用的parseSessionId方法,在最后调用了parseSessionCookiesId方法,因此,我们可以断定,tomcat将考虑url中的session id,然后再读取Cookie字段中的session id。还有就是在postParseRequest方法的最后部分有一个response.sendRedirect(redirectPath);,在调完它后,就直接return了。而没有执行到parseSessionCookiesId,因此,使用重定向并不能通过HTTP头的cookie字段共享session。只能通过url来传递session id。

(2) 将tomcat的cookie支持关闭

如果我们只想使用url来支持session,可以直接将tomcat的cookie功能关闭。我们可
以修改conf中的context.xml文件,加入一个cookies="false"即可,内容如下:

Java代码 复制代码
  1. <!-- The contents of this file will be loaded for each web application -->   
  2. <Context cookies = "false">   
  3. ... ...   
  4. ... ...   
  5. </Context>  



    重启tomcat后,就算客户端支持cookie,tomcat也不会考虑HTTP请求头的cookie字段。

(3) 在IE中控制Cookie

    在IE中也可以将Cookie关闭,启动IE,在工具->Internet选项->稳私->高级中选中"覆盖自动cookie处理"选项。并按图1选择:






对于下面的选项"总是允许会话cookie",如果不选,在本机将允许会话cookie,也就是通过localhost访问,在远程将不允许会话cookie。我们也可以通过在工具->Internet选项->稳私->站点来对某个网站来允许和拒绝cookie。

  • Cecb0536-a3fa-3eeb-bdaf-8a2ec0884053-thumb
  • 大小: 21.7 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics