关于使用会话追踪(session tracking)
?然后将重要的用户信息,如ID之类独一无二的数据,以隐藏字段的方式传送给服务器。隐藏字段的优点在于session数据传送到服务器端时,并不象GET的方法,会将session数据保露在URL之上。不过这种做法还是有它的缺点:一旦session数据储存在隐藏字段中,就仍然有暴露数据的危机,因为只要用户直接观看HTML的源文件,session数据将会暴露无疑。这将造成安全上的漏洞,特别当用户数据是依赖于用户ID、密码来取得的时候,将会有被盗用的危险。另外这种方法只适用特定的一个流程,不适用于通常意义的会话跟踪。
?
2.4:使用内建session对象
传统的会话追踪方式使用比较麻烦,Servlet的会话机制基于Cookie或URL重写技术,融合了这两种技术的优点。当客户端允许使用Cookie时,内建session对象使用Cookie进行会话追踪;如果客户端禁用Cookie,则选择使用URL重写。
?
?(1)获取session对象例如把购物车作为属性存储在session中,在其他JSP页面中可以通过session再获得购物车。
??// 在JSP页面中可以直接使用session
??ShoppingCart cart = (ShoppingCart)session.getAttribute("cart");
??内建的session对象是javax.servlet.http.HttpSession类的实例,如果在JavaBean或者Servlet中使用session就需要先从当前的request对象中取得,例如:
??// 得到用户session和购物篮
??HttpSession session = request.getSession();
??ShoppingCart cart = (ShoppingCart)session.getAttribute("cart");
?
?(2)读写session中的数据向session中存入对象使用setAttribute方法,通过getAttribute方法读取对象。从session返回的值注意要转换成合适的类型,要注意检查结??果是否为null。例如下面一段代码:
??HttpSession session = request.getSession();
??SomeClass value = (SomeClass)session.getAttribute("someID");
??if (value == null) {
??value = new SomeClass(...);
??session.setAttribute("someID", value);
??}
??doSomethingWith(value);
?
?(3)废弃session数据调用removeAttribute废弃session中的值,即移除与名称关联的值。
??调用invalidate废弃整个session,即废弃当前的session。
??如果用户注销离开站点,注意废弃与用户相关联的所有session。
?
?(4)session的生命周期由于没有办法知道HTTP客户端是否不再需要session,因此每个session都关联一个时间期限使它的资源可以被回收。setMaxInactiveInterval(int?secondsToLive)
?
?(5)服务器使用session时,默认使用Cookie技术进行会话追踪,通常,会话管理是通过服务器将 Session ID 作为一个 cookie 存储在用户的 Web 浏览器中,并用它来唯一标识每个用户会话。如果客户端不接受Cookie的时候,服务器可以利用URL重写的方式将sessionID作为参数附在URL后面,来实现会话管理。
?
当我们在进行forward,redirect时,一定要调用下边两个方法,以保持session一直有效(如果不调用的话,那么你到了新的页面时session就失效了,因为session ID没有传过来)
Servlet中Interface HttpServletResponse 规定了两个方法,response.encodeURL()或response.encodeRedirectURL()方法,这两个方法首先判断Cookies是否被浏览器支持;如果支持,则参数URL被原样返回,session?ID将通过Cookies来维持;否则返回带有sessionID的URL。Tomcat服务器实现了这两个方法。
??下面是使用encodeURL方法的示例,两个文件hello1.jsp和hello2.jsp。
??a:?hello1.jsp的完整程序代码如下:
??<%@ page contentType="text/html;charset=gb2312"%>
??<%String url =response.encodeURL("hello2.jsp");%>
??<a href='<%=url%>'>进入到hello2.jsp</a>
??b:?解释:
??hello1.jsp利用了response对象内的encodeURL方法,将URL做了一个编码动作。编码不是这里关心的重点,重点是如果浏览器的cookie被禁用的话那么象;jsessionid=A09F3A5583825EE787580106CC62A1E8??这样字符串就会被添加到 hello2.jsp 的后面,也就是告诉了下一个页面session的信息。
?
??b:?若要使用重定向,例如:
??response.sendRedirect("hello2.jsp");也应该改为:response.sendRedirect(response.encodeRedirectURL("hello2.jsp"));同时需要注意的是,将session的ID以URL的编码方式进行时,需将每一页都编码,才能保留住session的ID。如果遇到没有编码的URL,则无法进行会话跟踪。
??
??c:?hello2.jsp的完整程序代码如下:
??<%@ page contentType="text/html;charset=gb2312"%>
??<% out.println("sessionID is "+session.getId());%>
??d:?可以看到如果服务器使用URL重写,它将会话信息附加到URL上,如下所示:
??http://localhost:8080/ch09/hello2.jsp;jsessionid=A09F3A5583825EE787580106CC62A1E8
??
??e:?实质上 URL 重写是通过向 URL 连接添加参数,并把 session ID 作为值包含在连接中,以便应用服务器可以根据sessionID从cache中的取回session.
?
?
下面转自 http://hi.baidu.com/haojian/blog/item/d27b0e7b36a55ee60bd187ca.html
TOMCAT服务器
SESSION实现会话跟踪通常是cookie和url重写,如果浏览器不禁止cookie的话,tomcat优先使用cookie实现,否则它将使用URL重写来支持SESSION.
URL重写的额外数据是服务器自动添加的,那么服务器是怎么添加的呢?Tomcat在返回Response的时候,检查JSP页面中所有的URL,包括所有的链接,和 Form的Action属性,在这些URL后面加上“;jsessionid=xxxxxx”。添加url后缀的代码片段如下:
org.apache.coyote.tomcat5.CoyoteResponse类的toEncoded()方法支持URL重写。??
1 StringBuffer sb = new StringBuffer(path);
2???????? if( sb.length() > 0 ) { // jsessionid can't be first.
3???????????? sb.append(";jsessionid=");
4???????????? sb.append(sessionId);
5???????? }
6???????? sb.append(anchor);
7???????? sb.append(query);
8???????? return (sb.toString());
?
服务器端实现原理
Session在服务器端具体是怎么实现的呢?我们使用session的时候一般都是这么使用的:
request.getSession()或者request.getSession(true)。
这个时候,服务器就检查是不是已经存在对应的Session对象,见HttpRequestBase类
doGetSession(boolean create)方法:
?1? if ((session != null) && !session.isValid())
?2???????????? session = null;
?3???????? if (session != null)
?4???????????? return (session.getSession());
?5
?6
?7???????? // Return the requested session if it exists and is valid
?8???????? Manager manager = null;
?9???????? if (context != null)
10???????????? manager = context.getManager();
11???????? if (manager == null)
12???????????? return (null);????? // Sessions are not supported
13???????? if (requestedSessionId != null) {
14???????????? try {
15???????????????? session = manager.findSession(requestedSessionId);
16???????????? } catch (IOException e) {
17???????????????? session = null;
18???????????? }
19???????????? if ((session != null) && !session.isValid())
20???????????????? session = null;
21???????????? if (session != null) {
22???????????????? return (session.getSession());
23???????????? }
24???????? }
requestSessionId从哪里来呢?这个肯定是通过Session实现机制的cookie或URL重写来设置的。见HttpProcessor类中的parseHeaders(SocketInputStream input):
//From COOKIE
?1 for (int i = 0; i < cookies.length; i++) {
?2???????????????????? if (cookies[i].getName().equals
?3???????????????????????? (Globals.SESSION_COOKIE_NAME)) {//SESSION_COOKIE_NAME="JSESSIONID"???
?4???????????????????????? // Override anything requested in the URL
?5???????????????????????? if (!request.isRequestedSessionIdFromCookie()) {
?6???????????????????????????? // Accept only the first session id cookie
?7???????????????????????????? request.setRequestedSessionId
?8???????????????????????????????? (cookies[i].getValue());
?9???????????????????????????? request.setRequestedSessionCookie(true);
10???????????????????????????? request.setRequestedSessionURL(false);
11????????????????????????????
12???????????????????????? }
13???????????????????? }
14 }
//FROM URL REWRITE
或者HttpProcessor类中的parseRequest(SocketInputStream input, OutputStream output)
?1 // Parse any requested session ID out of the request URI
?2???????? int semicolon = uri.indexOf(match);? //match 是";jsessionid="字符串
?3???????? if (semicolon >= 0) {
?4???????????? String rest = uri.substring(semicolon + match.length());
?5???????????? int semicolon2 = rest.indexOf(';');
?6???????????? if (semicolon2 >= 0) {
?7???????????????? request.setRequestedSessionId(rest.substring(0, semicolon2));
?8???????????????? rest = rest.substring(semicolon2);
?9???????????? } else {
10???????????????? request.setRequestedSessionId(rest);
11???????????????? rest = "";
12???????????? }
13???????????? request.setRequestedSessionURL(true);
14???????????? uri = uri.substring(0, semicolon) + rest;
15???????????? if (debug >= 1)
16???????????????? log(" Requested URL session id is " +
17???????????????????? ((HttpServletRequest) request.getRequest())
18???????????????????? .getRequestedSessionId());
19???????? } else {
20???????????? request.setRequestedSessionId(null);
21???????????? request.setRequestedSessionURL(false);
22???????? }
23
里面的manager.findSession(requestSessionId)用于查找此会话ID对应的session对象。Tomcat实现
是通过一个HashMap实现,见ManagerBase.java的findSession(String id):
1???????? if (id == null)
2???????????? return (null);
3???????? synchronized (sessions) {
4???????????? Session session = (Session) sessions.get(id);
5???????????? return (session);
6???????? }
Session本身也是实现为一个HashMap,因为Session设计为存放key-value键值对,Tomcat里面Session实现类是StandardSession,里面一个attributes属性:
1????
4???? private HashMap attributes = new HashMap();
所有会话信息的存取都是通过这个属性来实现的。Session会话信息不会一直在服务器端保存,超过一定的时间期限就会被删除,这个时间期限可以在web.xml中进行设置,不设置的话会有一个默认值,Tomcat的默认值是60。那么服务器端是怎么判断会话过期的呢?原理服务器会启动一个线程,一直查询所有的Session对象,检查不活动的时间是否超过设定值,如果超过就将其删除。见StandardManager类,它实现了Runnable接口,里面的run方法如下:
?1????
?4???? public void run() {
?5
?6???????? // Loop until the termination semaphore is set
?7???????? while (!threadDone) {
?8???????????? threadSleep();
?9???????????? processExpires();
10???????? }
11
12???? }
13
14????
17???? private void processExpires() {
18
19???????? long timeNow = System.currentTimeMillis();
20???????? Session sessions[] = findSessions();
21
22???????? for (int i = 0; i < sessions.length; i++) {
23???????????? StandardSession session = (StandardSession) sessions[i];
24???????????? if (!session.isValid())
25???????????????? continue;
26???????????? int maxInactiveInterval = session.getMaxInactiveInterval();
27???????????? if (maxInactiveInterval < 0)
28???????????????? continue;
29???????????? int timeIdle = // Truncate, do not round up
30???????????????? (int) ((timeNow - session.getLastUsedTime()) / 1000L);
31???????????? if (timeIdle >= maxInactiveInterval) {
32???????????????? try {
33???????????????????? expiredSessions++;
34???????????????????? session.expire();
35???????????????? } catch (Throwable t) {
36???????????????????? log(sm.getString("standardManager.expireException"), t);
37???????????????? }
38???????????? }
39???????? }
40
41???? }