Tomcat中的Session小結

什麼是Session

對Tomcat而言,Session是一塊在服務器開闢的內存空間,其存儲結構爲ConcurrentHashMap;web

Session的目的

Http協議是一種無狀態協議,即每次服務端接收到客戶端的請求時,都是一個全新的請求,服務器並不知道客戶端的歷史請求記錄;chrome

Session的主要目的就是爲了彌補Http的無狀態特性。簡單的說,就是服務器能夠利用session存儲客戶端在同一個會話期間的一些操做記錄;數據庫

實現機制

先看兩個問題,以下:

一、服務器如何判斷客戶端發送過來的請求是屬於同一個會話?瀏覽器

答:用Session id區分,Session id相同的即認爲是同一個會話,在Tomcat中Session id用JSESSIONID表示;tomcat

二、服務器、客戶端如何獲取Session id?Session id在其之間是如何傳輸的呢?安全

答:服務器第一次接收到請求時,開闢了一塊Session空間(建立了Session對象),同時生成一個Session id,並經過響應頭的Set-Cookie:「JSESSIONID=XXXXXXX」命令,向客戶端發送要求設置cookie的響應;服務器

客戶端收到響應後,在本機客戶端設置了一個JSESSIONID=XXXXXXX的cookie信息,該cookie的過時時間爲瀏覽器會話結束;cookie

接下來客戶端每次向同一個網站發送請求時,請求頭都會帶上該cookie信息(包含Session id);session

而後,服務器經過讀取請求頭中的Cookie信息,獲取名稱爲JSESSIONID的值,獲得這次請求的Session id;數據結構

ps:服務器只會在客戶端第一次請求響應的時候,在響應頭上添加Set-Cookie:「JSESSIONID=XXXXXXX」信息,接下來在同一個會話的第二第三次響應頭裏,是不會添加Set-Cookie:「JSESSIONID=XXXXXXX」信息的;

而客戶端是會在每次請求頭的cookie中帶上JSESSIONID信息;

舉個例子:

以chrome瀏覽器爲例,訪問一個基於tomcat服務器的網站的時候,

瀏覽器第一次訪問服務器,服務器會在響應頭添加Set-Cookie:「JSESSIONID=XXXXXXX」信息,要求客戶端設置cookie,以下圖:

同時咱們也能夠在瀏覽器中找到其存儲的sessionid信息,以下圖

接下來,瀏覽器第二次、第三次...訪問服務器,觀察其請求頭的cookie信息,能夠看到JSESSIONID信息存儲在cookie裏,發送給服務器;且響應頭裏沒有Set-Cookie信息,以下圖:

只要瀏覽器未關閉,在訪問同一個站點的時候,其請求頭Cookie中的JSESSIONID都是同一個值,被服務器認爲是同一個會話。

 再舉個簡單的例子加深印象,新建個Web工程,並寫一個Servlet,在doGet中添加以下代碼,主要作以下工做

首先,從session中獲取key爲count的值,累加,存入session,並打印;

而後,每次從請求中獲取打印cookie信息,從響應中獲取打印Header的Set-Cookie信息:

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        if(request.getSession().getAttribute("count") == null){
            request.getSession().setAttribute("count", 0);
            response.getWriter().write(0+"");
        }else{
            int a = Integer.parseInt(request.getSession().getAttribute("count").toString());
            request.getSession().setAttribute("count", ++a);
            response.getWriter().write(a+"");
        }

        Cookie[] cookies = request.getCookies();
        StringBuffer sb = new StringBuffer();
        if(cookies!=null){
            for(Cookie cookie : cookies){
                sb.append(cookie.getName()+":"+cookie.getValue()+",");
            }
            sb.deleteCharAt(sb.length()-1);
        }

        System.out.println("[第"+(++index)+"次訪問]from client request, cookies:" + sb);
        System.out.println("[第"+(index)+"次訪問]from server response, header-Set-Cookie:" + response.getHeader("Set-Cookie"));;
    }

部署到tomcat後,連續訪問該servlet,觀察控制檯輸出,以下,客戶端第一次訪問服務器的時候,在服務端的響應頭裏添加了JSESSIONID信息,且接下來客戶端的每次訪問都會帶上該JSESSIONID:

其實這裏有一個問題,session劫持

只要用戶知道JSESSIONID,該用戶就能夠獲取到JSESSIONID對應的session內容,仍是以上面這個例子爲例,

我先用IE瀏覽器訪問該站點,好比連續訪問了5次,此時,session中的count值爲:

查看該會話的Session id,爲6A541281A79B24BC290ED3270CF15E32

接下來打開chrome控制檯,將IE瀏覽器獲取過來的JSESSIONID信息(「6A541281A79B24BC290ED3270CF15E32」)寫入到cookie中,以下

接着刪除其中的一個,只留下JSESSIONID爲「6A541281A79B24BC290ED3270CF15E32」的cookie;

刷新頁面,發現咱們從session獲取的count值已經變成6了,說明這次chrome瀏覽器的請求劫持了IE瀏覽器會話中的session,

Tomcat中的session實現

Tomcat中一個會話對應一個session,其實現類是StandardSession,查看源碼,能夠找到一個attributes成員屬性,即存儲session的數據結構,爲ConcurrentHashMap,支持高併發的HashMap實現;

    /**
     * The collection of user data attributes associated with this Session.
     */
    protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();

那麼,tomcat中多個會話對應的session是由誰來維護的呢?ManagerBase類,查看其代碼,能夠發現其有一個sessions成員屬性,存儲着各個會話的session信息:

    /**
     * The set of currently active Sessions for this Manager, keyed by
     * session identifier.
     */
    protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();

接下來,看一下幾個重要的方法,

服務器查找Session對象的方法

客戶端每次的請求,tomcat都會在HashMap中查找對應的key爲JSESSIONID的Session對象是否存在,能夠查看Request的doGetSession方法源碼,以下源碼:

protected Session doGetSession(boolean create) {

        // There cannot be a session if no context has been assigned yet
        Context context = getContext();
        if (context == null) {
            return (null);
        }

        // Return the current session if it exists and is valid
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        if (session != null) {
            return (session);
        }

        // Return the requested session if it exists and is valid
        Manager manager = context.getManager();
        if (manager == null) {
            return null;        // Sessions are not supported
        }
        if (requestedSessionId != null) {
            try {
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            if ((session != null) && !session.isValid()) {
                session = null;
            }
            if (session != null) {
                session.access();
                return (session);
            }
        }

        // Create a new session if requested and the response is not committed
        if (!create) {
            return (null);
        }
        if ((context != null) && (response != null) &&
            context.getServletContext().getEffectiveSessionTrackingModes().
                    contains(SessionTrackingMode.COOKIE) &&
            response.getResponse().isCommitted()) {
            throw new IllegalStateException
              (sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        // Re-use session IDs provided by the client in very limited
        // circumstances.
        String sessionId = getRequestedSessionId();
        if (requestedSessionSSL) {
            // If the session ID has been obtained from the SSL handshake then
            // use it.
        } else if (("/".equals(context.getSessionCookiePath())
                && isRequestedSessionIdFromCookie())) {
            /* This is the common(ish) use case: using the same session ID with
             * multiple web applications on the same host. Typically this is
             * used by Portlet implementations. It only works if sessions are
             * tracked via cookies. The cookie must have a path of "/" else it
             * won't be provided to for requests to all web applications.
             *
             * Any session ID provided by the client should be for a session
             * that already exists somewhere on the host. Check if the context
             * is configured for this to be confirmed.
             */
            if (context.getValidateClientProvidedNewSessionId()) {
                boolean found = false;
                for (Container container : getHost().findChildren()) {
                    Manager m = ((Context) container).getManager();
                    if (m != null) {
                        try {
                            if (m.findSession(sessionId) != null) {
                                found = true;
                                break;
                            }
                        } catch (IOException e) {
                            // Ignore. Problems with this manager will be
                            // handled elsewhere.
                        }
                    }
                }
                if (!found) {
                    sessionId = null;
                }
                sessionId = getRequestedSessionId();
            }
        } else {
            sessionId = null;
        }
        session = manager.createSession(sessionId);

        // Creating a new session cookie based on that session
        if ((session != null) && (getContext() != null)
               && getContext().getServletContext().
                       getEffectiveSessionTrackingModes().contains(
                               SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());

            response.addSessionCookieInternal(cookie);
        }

        if (session == null) {
            return null;
        }

        session.access();
        return session;
    }
View Code

先看doGetSession方法中的以下代碼,這個通常是第一次訪問的狀況,即建立session對象,session的建立是調用了ManagerBase的createSession方法來實現的; 另外,注意response.addSessionCookieInternal方法,該方法的功能就是上面提到的往響應頭寫入「Set-Cookie」信息;最後,還要調用session.access方法記錄下該session的最後訪問時間,由於session是能夠設置過時時間的;

        session = manager.createSession(sessionId);

        // Creating a new session cookie based on that session
        if ((session != null) && (getContext() != null)
               && getContext().getServletContext().
                       getEffectiveSessionTrackingModes().contains(
                               SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());

            response.addSessionCookieInternal(cookie);
        }

        if (session == null) {
            return null;
        }

        session.access();
        return session;

再看doGetSession方法中的以下代碼,這個通常是第二次之後訪問的狀況,經過ManagerBase的findSession方法查找session,其實就是利用map的key從ConcurrentHashMap中拿取對應的value,這裏的key即requestedSessionId,也即JSESSIONID,同時還要調用session.access方法,記錄下該session的最後訪問時間;

        if (requestedSessionId != null) { try { session = manager.findSession(requestedSessionId); } catch (IOException e) { session = null; } if ((session != null) && !session.isValid()) { session = null; } if (session != null) { session.access(); return (session); } }

在session對象中查找和設置key-value的方法

這個咱們通常調用getAttribute/setAttribute方法:

getAttribute方法很簡單,就是根據key從map中獲取value;

setAttribute方法稍微複雜點,除了設置key-value外,若是添加了一些事件監聽(HttpSessionAttributeListener)的話,還要通知執行,如beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。

session存在的問題

  • 安全性,session劫持,這個前面已經舉過例子了;
  • 增長服務器壓力,由於session是直接存儲在服務器的內存中的;
  • 若是存在多臺服務器的話,還存在session同步問題,固然若是隻有一臺tomcat服務器的話,也就沒有session同步的事情了,然而如今通常的應用都會用到多臺tomcat服務器,經過負載均衡,同一個會話有可能會被分配到不一樣的tomcat服務器,所以極可能出現session不一致問題;解決session同步問題,實際上主要是保證可以抽離出一塊共享空間存放session信息,且這塊空間不一樣的tomcat服務器均可以訪問到;通常這塊共享的空間能夠是數據庫,或者某臺服務器的內存空間,甚至硬盤空間,或者客戶端的cookie也是能夠的;
相關文章
相關標籤/搜索