浏览 4450 次
精华帖 (0) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-06-01
Tomcat中主要由每个context容器内的一个Manager对象来管理session。对于这个manager对象的实现,可以根据tomcat提供的接口或基类来自己定制,同时,tomcat也提供了标准实现。 在tomcat架构分析(容器类)中已经介绍过,在每个context对象,即web app都具有一个独立的manager对象。通过server.xml可以配置定制化的manager,也可以不配置。不管怎样,在生成context对象时,都会生成一个manager对象。缺省的是StandardManager类,其类路径为: 引用 org.apache.catalina.session.StandardManager
Session对象也可以定制化实现,其主要实现标准servlet的session接口: 引用 javax.servlet.http.HttpSession
Tomcat也提供了标准的session实现: 引用 org.apache.catalina.session.StandardSession
本文主要就是结合消息流程介绍这两个类的实现,及session机制。 Session方面牵涉的东西还是蛮多的,例如HA,session复制是其中重要部分等,不过本篇主要从功能方面介绍session管理,有时间再说说扩展。 Session管理主要涉及到这几个方面:
创建session 在具体说明session的创建过程之前,先看一下BS访问模型吧,这样理解直观一点。
以上是session的获取及创建过程。在servlet中获取session,通常是调用request的getSession方法。这个方法需要传入一个boolean参数,这个参数就是实现刚才说的,当jsessionid为空或从session池中获取不到相应的session对象时,选择创建一个新的session还是不创建。 看一下核心代码逻辑; protected Session doGetSession(boolean create) { …… // 先获取所在context的manager对象 Manager manager = null; if (context != null) manager = context.getManager(); if (manager == null) return (null); // Sessions are not supported //这个requestedSessionId就是从Http request中解析出来的 if (requestedSessionId != null) { try { //manager管理的session池中找相应的session对象 session = manager.findSession(requestedSessionId); } catch (IOException e) { session = null; } //判断session是否为空及是否过期超时 if ((session != null) && !session.isValid()) session = null; if (session != null) { //session对象有效,记录此次访问时间 session.access(); return (session); } } // 如果参数是false,则不创建新session对象了,直接退出了 if (!create) return (null); if ((context != null) && (response != null) && context.getCookies() && response.getResponse().isCommitted()) { throw new IllegalStateException (sm.getString("coyoteRequest.sessionCreateCommitted")); } // 开始创建新session对象 if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) { session = manager.createSession(getRequestedSessionId()); } else { session = manager.createSession(null); } // 将新session的jsessionid写入cookie,传给browser if ((session != null) && (getContext() != null) && getContext().getCookies()) { Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal()); configureSessionCookie(cookie); response.addCookieInternal(cookie); } //记录session最新访问时间 if (session != null) { session.access(); return (session); } else { return (null); } } 尽管不能贴出所有代码,但是上述的核心逻辑还是很清晰的。从中也可以看出,我们经常在servlet中这两种调用方式的不同; 新创建session 引用 request.getSession(); 或者request.getSession(true);
不创建session 引用 request.getSession(false);
接下来,看一下StandardManager的createSession方法,了解一下session的创建过程; public Session createSession(String sessionId) { //这是个session数量控制逻辑,超过上限则抛异常退出 if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) { rejectedSessions++; throw new IllegalStateException (sm.getString("standardManager.createSession.ise")); } return (super.createSession(sessionId)); } 这个最大支持session数量maxActiveSessions是可以配置的,先不管这个安全控制逻辑,看其主逻辑,即调用其基类的createSession方法; public Session createSession(String sessionId) { // 创建一个新的StandardSession对象 Session session = createEmptySession(); // Initialize the properties of the new session and return it session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); if (sessionId == null) { //设置jsessionid sessionId = generateSessionId(); } session.setId(sessionId); sessionCounter++; return (session); } 关键是jsessionid的产生过程,接着看generateSessionId方法; protected synchronized String generateSessionId() { byte random[] = new byte[16]; String jvmRoute = getJvmRoute(); String result = null; // Render the result as a String of hexadecimal digits StringBuffer buffer = new StringBuffer(); do { int resultLenBytes = 0; if (result != null) { buffer = new StringBuffer(); duplicates++; } while (resultLenBytes < this.sessionIdLength) { getRandomBytes(random); random = getDigest().digest(random); for (int j = 0; j < random.length && resultLenBytes < this.sessionIdLength; j++) { byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2 < 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if (jvmRoute != null) { buffer.append('.').append(jvmRoute); } result = buffer.toString(); //注意这个do…while结构 } while (sessions.containsKey(result)); return (result); } 这里主要说明的不是生成jsessionid的算法了,而是这个do…while结构。把这个逻辑抽象出来,可以看出; 如图所示,创建jsessionid的方式是由tomcat内置的加密算法算出一个随机的jsessionid,如果此jsessionid已经存在,则重新计算一个新的,直到确保现在计算的jsessionid唯一。 好了,至此一个session就这么创建了,像上面所说的,返回时是将jsessionid以HTTP response的header:“Set-cookie”发给客户端。 注销session
Session创建完之后,不会一直存在,或是主动注销,或是超时清除。即是出于安全考虑也是为了节省内存空间等。例如,常见场景:用户登出系统时,会主动触发注销操作。 主动注销 主动注销时,是调用标准的servlet接口: 引用 session.invalidate();
看一下tomcat提供的标准session实现(StandardSession) public void invalidate() { if (!isValidInternal()) throw new IllegalStateException (sm.getString("standardSession.invalidate.ise")); // 明显的注销方法 expire(); } Expire方法的逻辑稍后再说,先看看超时注销,因为它们调用的是同一个expire方法。 超时注销 Tomcat定义了一个最大空闲超时时间,也就是说当session没有被操作超过这个最大空闲时间时间时,再次操作这个session,这个session就会触发expire。 这个方法封装在StandardSession中的isValid()方法内,这个方法在获取这个request请求对应的session对象时调用,可以参看上面说的创建session环节。也就是说,获取session的逻辑是,先从manager控制的session池中获取对应jsessionid的session对象,如果获取到,就再判断是否超时,如果超时,就expire这个session了。 看一下tomcat提供的标准session实现(StandardSession) public boolean isValid() { …… //这就是判断距离上次访问是否超时的过程 if (maxInactiveInterval >= 0) { long timeNow = System.currentTimeMillis(); int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L); if (timeIdle >= maxInactiveInterval) { expire(true); } } return (this.isValid); } Expire方法 是时候来看看expire方法了。 public void expire(boolean notify) { synchronized (this) { ...... //设立标志位 setValid(false); //计算一些统计值,例如此manager下所有session平均存活时间等 long timeNow = System.currentTimeMillis(); int timeAlive = (int) ((timeNow - creationTime)/1000); synchronized (manager) { if (timeAlive > manager.getSessionMaxAliveTime()) { manager.setSessionMaxAliveTime(timeAlive); } int numExpired = manager.getExpiredSessions(); numExpired++; manager.setExpiredSessions(numExpired); int average = manager.getSessionAverageAliveTime(); average = ((average * (numExpired-1)) + timeAlive)/numExpired; manager.setSessionAverageAliveTime(average); } // 将此session从manager对象的session池中删除 manager.remove(this); ...... } } 不需要解释,已经很清晰了。 这个超时时间是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各个app下的web.xml自行定义; <session-config> <session-timeout>30</session-timeout> </session-config> 单位是分钟。 Session持久化及启动初始化 这个功能主要是,当tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。当启动tomcat时,会从这个文件读入session,并添加到manager的session池中去。 这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。 看一下概念图吧,觉得不是重要实现逻辑,代码就不说了。 总结 由此可以看出,session的管理是容器层做的事情,应用层一般不会参与session的管理,也就是说,如果在应用层获取到相应的session,已经是由tomcat提供的,因此如果过多的依赖session机制来进行一些操作,例如访问控制,安全登录等就不是十分的安全,因为如果有人能得到正在使用的jsessionid,则就可以侵入系统。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |