HttpSession 原理


使用 HttpSession 進行會話管理十分方便,讓 Web 應用程式看似可以「記得」是瀏覽器發出的請求,連接數個請求間的關係。但無論如何,Web 應用程式是基於 HTTP 協定的事實並沒有改變,實際上如何「得知」數個請求之間的關係,這件工作是由 Web 容器幫你執行。

當你嘗試執行 HttpServletRequestgetSession() 時,Web容器會為你建立 HttpSession 物件,每個 HttpSession 物件都會有個特殊的 ID,稱之為Session ID,你可以執行 HttpSessiongetId() 來取得 Session ID。這個 Session ID 預設會使用 Cookie 存放至瀏覽器,Cookie 的名稱預設是 JSESSIONID,數值則是 getId() 取得的 Session ID。

HttpSession 原理

由於 Web 容器本身是執行於 JVM 中的一個 Java 程式,透過 getSession() 取得的 HttpSession,則是 Web 容器中的一個 Java 物件,HttpSession 存放的屬性,自然也就存放於伺服端的 Web 容器之中。每個 HttpSession 各有特殊的Session ID,當瀏覽器請求應用程式時,會將Cookie中存放的Session ID一併發送給應用程式,Web 容器根據 Session ID 來找出對應的 HttpSession 物件,如此就可以取得各瀏覽器個別的會話資料。

HttpSession 原理

使用 HttpSession 來進行會話管理時,設定為屬性的資料是儲存在伺服端,而 Session ID 預設使用 Cookie 存放於瀏覽器。Web 容器儲存 Session ID 的 Cookie 預設為關閉瀏覽器就失效,所以當你重新開啟瀏覽器請求應用程式時,透過 getSession() 取得的就是新的 HttpSession 物件。

每次請求來到應用程式時,容器會根據發送過來的 Session ID 取得對應的 HttpSession。由於 HttpSession 物件會佔用記憶體空間,所以 HttpSession 的屬性中儘量不要儲存耗資源的大型物件,必要時將屬性移除,或者不需使用 HttpSession 時,執行 invalidate()HttpSession 失效。

注意!預設關閉瀏覽器會馬上失效的是瀏覽器上的 Cookie,不是伺服器上的 HttpSession 物件。因為 Cookie 失效了,就無法透過 Cookie 來發送 Session ID,所以嘗試 getSession() 時,容器會產生新的 HttpSession。要讓 HttpSession 立即失效必須執行 invalidate() 方法,否則的話,HttpSession 會等到設定的失效期間過後才會被容器銷毀回收。

你可以執行 HttpSessionsetMaxInactiveInterval() 方法,設定瀏覽器多久沒有請求應用程式的話, HttpSession 就自動失效,設定的單位是「秒」。你也可以在 web.xml 中設定 HttpSession 預設的失效時間,但要特別注意!設定的時間單位是「分鐘」。例如:

</web-app …>
    ...
    <session-config>
        <session-timeout>30</session-timeout> <!-- 30 分鐘 -->
    </session-config>
</web-app>

注意!使用 HttpSession,預設是使用 Cookie 儲存 Session ID,但你不用介入操作 Cookie 的細節,容器會幫你完成相關操作。特別注意的是,執行 HttpSessionsetMaxInactiveInterval() 方法,所設定的是 HttpSession 物件在瀏覽器多久沒活動就失效的時間,而不是儲存 Session ID 的 Cookie 失效時間。儲存 Session ID 的 Cookie 預設為關閉瀏覽器就失效。

在 Servlet 4.0 中,HttpSession 預設失效時間,也可以透過 ServletContextsetSessionTimeout() 來設定。

在 Servlet 3.0 中新增了 SessionCookieConfig 介面,你可以透過 ServletContextgetSessionCookieConfig() 來取得實作該介面的物件,要取得 ServletContext 的話,則可以透過 Servlet 實例的 getServletContext() 來取得(關於 ServletContext,之後還會介紹)。

透過 SessionCookieConfig 實作物件,你可以設定儲存 Session ID 的 Cookie 相關資訊,例如可以透過 setName() 將預設的 Session ID 名稱修改為別的名稱,透過 setMaxAge() 設定儲存 Session ID 的 Cookie 存活期限(單位是秒)等。

但是要注意的是,設定 SessionCookieConfig 必須在 ServletContext 被初始化之前,所以實際上你要修改 Session ID、Cookie 存活期等資訊時,必須在 web.xml 中設定。例如:

</web-app …>
    ...
    <session-config>
        <session-timeout>30</session-timeout> <!-- 30 分鐘 -->
        <cookie-config>
            <name>yourJsessionid</name>
            <secure>true</secure>
            <http-only>true</http-only>
            <max-age>1800</max-age> <!-- 1800 秒,不建議 -->
        </cookie-config>
    </session-config>
</web-app>

另一個方法則是實作 ServletContextListener,容器在初始化 ServletContext 時會呼叫 ServletContextListenercontextInitialized() 方法,可以在其中取得 ServletContext 進行設定 SessionCookieConfig。關於 ServletContextListener 之後還會說明。

由於許多應用程式,都會在 HttpSession 中置放代表已登入的 Token 屬性,之後藉此判斷使用者是否登入,這表示只有有人可以拿到 Session ID(Session Hijacking),或者令客戶端使用特定的 Session ID(Session Fixation),就能達到入侵的可能性。

因此,建議不採用預設的 Session ID 名稱、在加密連線中傳遞 Session ID、設定 HTTP-Only 等,在使用者登入成功之後,變更 Session ID 以防止客戶端被指定了特定的 Session ID,而將重要的登入 Token 等資訊存入了特定的 HttpSession