tomcat之Session的管理
Session是由服務(wù)器端的應(yīng)用服務(wù)器容器(如Tomcat、Jetty)存儲的。下面分析一下Tomcat是如何管理Session的。
轉(zhuǎn)自:tomcat架構(gòu)分析 (Session管理)
Tomcat中主要由每個context容器內(nèi)的一個Manager對象來管理session。對于這個manager對象的實現(xiàn),可以根據(jù)tomcat提供的接口或基類來自己定制,同時,tomcat也提供了標準實現(xiàn)。
在每個context對象,即web app都具有一個獨立的manager對象。通過server.xml可以配置定制化的manager,也可以不配置。不管怎樣,在生成context對象時,都會生成一個manager對象。缺省的是StandardManager類,其類路徑為:
Session對象也可以定制化實現(xiàn),其主要實現(xiàn)標準servlet的session接口:
Tomcat也提供了標準的session實現(xiàn):
Session管理主要涉及到這幾個方面:
- 創(chuàng)建session
- 注銷session
- 持久化及啟動加載session
創(chuàng)建session
在具體說明session的創(chuàng)建過程之前,先看一下BS訪問模型吧,這樣理解直觀一點。

- browser發(fā)送Http request;
- tomcat內(nèi)核Http11Processor會從HTTP request中解析出“jsessionid”(具體的解析過程為先從request的URL中解析,這是為了有的瀏覽器把cookie功能禁止后,將URL重寫考慮的,如果解析不出來,再從cookie中解析相應(yīng)的jsessionid),解析完后封裝成一個request對象(當然還有其他的http header);
- servlet中獲取session,其過程是根據(jù)剛才解析得到的jsessionid(如果有的話),從session池(session maps)中獲取相應(yīng)的session對象;這個地方有個邏輯,就是如果jsessionid為空的話(或者沒有其對應(yīng)的session對象,或者有session對象,但此對象已經(jīng)過期超時),可以選擇創(chuàng)建一個session,或者不創(chuàng)建;
- 如果創(chuàng)建新session,則將session放入session池中,同時將與其相對應(yīng)的jsessionid寫入cookie通過Http response header的方式發(fā)送給browser,然后重復(fù)第一步。
以上是session的獲取及創(chuàng)建過程。在servlet中獲取session,通常是調(diào)用request的getSession方法。這個方法需要傳入一個boolean參數(shù),這個參數(shù)就是實現(xiàn)剛才說的,當jsessionid為空或從session池中獲取不到相應(yīng)的session對象時,選擇創(chuàng)建一個新的session還是不創(chuàng)建。
看一下核心代碼邏輯;
- 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池中找相應(yīng)的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);
- }
- }
- // 如果參數(shù)是false,則不創(chuàng)建新session對象了,直接退出了
- if (!create)
- return (null);
- if ((context != null) && (response != null) &&
- context.getCookies() &&
- response.getResponse().isCommitted()) {
- throw new IllegalStateException
- (sm.getString("coyoteRequest.sessionCreateCommitted"));
- }
- // 開始創(chuàng)建新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);
- }
- }
盡管不能貼出所有代碼,但是上述的核心邏輯還是很清晰的。從中也可以看出,我們經(jīng)常在servlet中這兩種調(diào)用方式的不同;
新創(chuàng)建session
不創(chuàng)建session
接下來,看一下StandardManager的createSession方法,了解一下session的創(chuàng)建過程;
- public Session createSession(String sessionId) {
- 是個session數(shù)量控制邏輯,超過上限則拋異常退出
- if ((maxActiveSessions >= 0) &&
- (sessions.size() >= maxActiveSessions)) {
- rejectedSessions++;
- throw new IllegalStateException
- (sm.getString("standardManager.createSession.ise"));
- }
- return (super.createSession(sessionId));
- }
這個最大支持session數(shù)量maxActiveSessions是可以配置的,先不管這個安全控制邏輯,看其主邏輯,即調(diào)用其基類的createSession方法;
- public Session createSession(String sessionId) {
- // 創(chuàng)建一個新的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) {
- //設(shè)置jsessionid
- sessionId = generateSessionId();
- }
- session.setId(sessionId);
- sessionCounter++;
- return (session);
- }
關(guān)鍵是jsessionid的產(chǎn)生過程,接著看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結(jié)構(gòu)
- } while (sessions.containsKey(result));
- return (result);
- }
這里主要說明的不是生成jsessionid的算法了,而是這個do…while結(jié)構(gòu)。把這個邏輯抽象出來,可以看出;
如圖所示,創(chuàng)建jsessionid的方式是由tomcat內(nèi)置的加密算法算出一個隨機的jsessionid,如果此jsessionid已經(jīng)存在,則重新計算一個新的,直到確?,F(xiàn)在計算的jsessionid唯一。
好了,至此一個session就這么創(chuàng)建了,像上面所說的,返回時是將jsessionid以HTTP response的header:“Set-cookie”發(fā)給客戶端。
注銷session
- 主動注銷
- 超時注銷
Session創(chuàng)建完之后,不會一直存在,或是主動注銷,或是超時清除。即是出于安全考慮也是為了節(jié)省內(nèi)存空間等。例如,常見場景:用戶登出系統(tǒng)時,會主動觸發(fā)注銷操作。
主動注銷
主動注銷時,是調(diào)用標準的servlet接口:
看一下tomcat提供的標準session實現(xiàn)(StandardSession)
- public void invalidate() {
- if (!isValidInternal())
- throw new IllegalStateException
- (sm.getString("standardSession.invalidate.ise"));
- // 明顯的注銷方法
- expire();
- }
Expire方法的邏輯稍后再說,先看看超時注銷,因為它們調(diào)用的是同一個expire方法。
超時注銷
Tomcat定義了一個最大空閑超時時間,也就是說當session沒有被操作超過這個最大空閑時間時間時,再次操作這個session,這個session就會觸發(fā)expire。
這個方法封裝在StandardSession中的isValid()方法內(nèi),這個方法在獲取這個request請求對應(yīng)的session對象時調(diào)用,可以參看上面說的創(chuàng)建session環(huán)節(jié)。也就是說,獲取session的邏輯是,先從manager控制的session池中獲取對應(yīng)jsessionid的session對象,如果獲取到,就再判斷是否超時,如果超時,就expire這個session了。
看一下tomcat提供的標準session實現(xiàn)(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) {
- ......
- //設(shè)立標志位
- setValid(false);
- //計算一些統(tǒng)計值,例如此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);
- ......
- }
- }
不需要解釋,已經(jīng)很清晰了。
這個超時時間是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各個app下的web.xml自行定義;
- <session-config>
- <session-timeout>30</session-timeout>
- </session-config>
單位是分鐘。
Session持久化及啟動初始化
這個功能主要是,當tomcat執(zhí)行安全退出時(通過執(zhí)行shutdown腳本),會將session持久化到本地文件,通常在tomcat的部署目錄下有個session.ser文件。當啟動tomcat時,會從這個文件讀入session,并添加到manager的session池中去。
這樣,當tomcat正常重啟時, session沒有丟失,對于用戶而言,體會不到重啟,不影響用戶體驗。
看一下概念圖吧,覺得不是重要實現(xiàn)邏輯,代碼就不說了。
總結(jié)
由此可以看出,session的管理是容器層做的事情,應(yīng)用層一般不會參與session的管理,也就是說,如果在應(yīng)用層獲取到相應(yīng)的session,已經(jīng)是由tomcat提供的,因此如果過多的依賴session機制來進行一些操作,例如訪問控制,安全登錄等就不是十分的安全,因為如果有人能得到正在使用的jsessionid,則就可以侵入系統(tǒng)。
自己的個人博客:bingtel-木猶如此的博客
浙公網(wǎng)安備 33010602011771號