您的位置:首页 > 其它

Session原理和会话跟踪登录实现方式分析

2013-07-05 21:49 573 查看
Session的创建和管理机制如下图所示:



由于HTTP是无状态的协议,客户程序每次都去web页面,都打开到web服务器的单独的连接,并且不维护客户的上下文信息。如果需要维护上下文信息,比如用户登录系统后,每次都能够知道操作的是此登录用户,而不是其他用户。对于这个问题,存在三种解决方案:cookie,url重写和隐藏表单域。

1、cookie

cookie是一个服务器和客户端相结合的技术,服务器可以将会话ID发送到浏览器,浏览器将此cookie信息保存起来,后面再访问网页时,服务器又能够从浏览器中读到此会话ID,通过这种方式判断是否是同一用户。

GET /bbs/admin/top.jsp HTTP/1.1
Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost:8080/bbs/admin/backGround.jsp Accept-Language: zh-CN
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C; Tablet PC 2.0)
Host: localhost:8080
Connection: Keep-Alive
Cookie: JSESSIONID=425436B1E13327552467F065B8357B37


2、URL重写

重写这种方式,客户端程序在每个URL的尾部自动添加一些额外数据,这些数据以表示这个会话,比如
http://192.168.1.20:8080/crm/getuserprofile.html;jsessionid=abc123。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());

从上面URL的实现原理可知,URL重写有一个缺点:在你的站点上不能有任何静态的HTML页面(至少静态页面中不能有任何链接到站点动态页面的链接)。因此,每个页面都必须使用servlet或 JSP动态生成。即使所有的页面都动态生成,如果用户离开了会话并通过书签或链接再次回来,会话的信息都会丢失,因为存储下来的链接含有错误的标识信息- 该URL后面的SESSION ID已经过期了。其实经过本人的实践,只要在在第一次通过jsessionId验证登录之后通过下面的代码:

Cookie cookie=new Cookie("JSESSIONID", req.getSession().getId());

resp.addCookie(cookie);

的方式把jsessionid写到客户端,那么当第二次登录之后客户端的cookie里面就已经拥有了jsessionid,此时就算不用加上jsessionid也可以直接访问后台页面了。

3、隐藏表单域

这种方式借助html表单中的hidden来实现,适用特定的一个流程,但是不适用于通常意义的会话跟踪。

综上所述,session实现会话跟踪通常是cookie和url重写,如果浏览器不禁止cookie的话,tomcat优先使用cookie实现,这种方式适合在服务端通过Session监听器和HashMap来管理Session时适用。

服务器端实现原理

当服务器接收到客户端同时发过来的Cookie:Jsessionid和UL重写的Jsessionid时,会优先获取URL重写的Jsessionid去获取Session。

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):

1 for (int i = 0; i < cookies.length; i++) {

2 if (cookies[i].getName().equals

3 (Globals.SESSION_COOKIE_NAME)) {

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 }

或者HttpOrocessor类中的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 /**

2 * The collection of user data attributes associated with this Session.

3 */

4 private HashMap attributes = new HashMap();

所有会话信息的存取都是通过这个属性来实现的。Session会话信息不会一直在服务器端保存,超过一定的时间期限就会被删除,这个时间期限可以在web.xml中进行设置,不设置的话会有一个默认值,Tomcat的默认值是60。那么服务器端是怎么判断会话过期的呢?原理服务器会启动一个线程,一直查询所有的Session对象,检查不活动的时间是否超过设定值,如果超过就将其删除。见StandardManager类,它实现了Runnable接口,里面的run方法如下:

1 /**

2 * The background thread that checks for session timeouts and shutdown.

3 */

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 /**

15 * Invalidate all sessions that have expired.

16 */

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 }

Session信息在create,expire等事情的时候都会触发相应的Listener事件,从而可以对session信息进行监控,这些Listener只需要继承HttpSessionListener,并配置在web.xml文件中。如下是一个监控在线会话数的Listerner:

import java.util.HashSet;

import javax.servlet.ServletContext;

import javax.servlet.http.HttpSession;

import javax.servlet.http.HttpSessionEvent;

import javax.servlet.http.HttpSessionListener;

public class MySessionListener implements HttpSessionListener {

public void sessionCreated(HttpSessionEvent event) {

HttpSession session = event.getSession();

ServletContext application = session.getServletContext();

// 在application范围由一个HashSet集保存所有的session

HashSet sessions = (HashSet) application.getAttribute("sessions");

if (sessions == null) {

sessions = new HashSet();

application.setAttribute("sessions", sessions);

}

// 新创建的session均添加到HashSet集中

sessions.add(session);

// 可以在别处从application范围中取出sessions集合

// 然后使用sessions.size()获取当前活动的session数,即为“在线人数”

}

public void sessionDestroyed(HttpSessionEvent event) {

HttpSession session = event.getSession();

ServletContext application = session.getServletContext();

HashSet sessions = (HashSet) application.getAttribute("sessions");

// 销毁的session均从HashSet集中移除

sessions.remove(session);

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: