SpringBoot2.x系列教程73--Web開發中的Session詳解及原理分析

SpringBoot2.x系列教程73--Web開發中的Session詳解及原理分析

一. HttpSession機制

1. HTTP協議與HttpSession的狀態保持

Session經常被翻譯爲會話,其本來的含義是指有始有終的一系列動作/消息。比如打電話時,從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之爲一個Session。而在網絡中,Session是指從一個瀏覽器窗口打開再到關閉的這個期間。

互聯網應用層協議基本都是基於 HTTP 和 HTTPS 協議的,它們的本身都是無狀態的, 也就是隻負責網絡的請求和響應。 我們只需要告訴服務器我們需要什麼,服務器就會給我們返回相應的資源。 如果沒有額外處理的話,服務器並不知道發起請求的人是誰,也無法根據請求者是誰來給你展現和你相關的內容了。

HTTP 協議之所以一開始被設計成這樣,還是有一些歷史原因的,當時的互聯網多用於學術交流,只用於文章信息的展現,遠沒有像現在這麼豐富多彩。所以在當時的背景下, HTTP 協議被設計成這樣,其實也是很符合它的場景的。

但隨着互聯網應用越來越廣泛,應用的形式也變得越來越多,我們的 Web 應用已不只限於提供簡單的信息展現了,還需要用戶能夠與服務器進行交互,比如能夠登錄,可以在留言回覆、可以進行購物、社交等。 這就需要 HTTP 協議能夠記錄用戶的狀態,而這個狀態就可以由HttpSession來進行保存。

2. HttpSession工作原理

  • Client(瀏覽器)第一次發送請求的時候,Web Container(Tomcat、Jetty等服務器容器)會生成唯一的Session ID(這個Session ID包括隨機數+時間+JVM ID),並將其返回給Client(在Web Container返回給Client的Response中),但是Web Container上的這個HttpSession是臨時的。

  • 接下來Client在每次發送請求給服務器時,都會將Session ID發送給Web Container,這樣Web Container就能很容易區分出是哪個Client。

  • Web Container會使用這個Session ID,找到對應的HttpSession,並將這個Request與這個HttpSession聯繫起來。

  1. 當用戶第一次訪問Servlet時,服務器端會給用戶客戶端創建一個獨立的Session;
  2. 該Session會有一個Session ID(JSESSIONID),格式如:JSESSIONID=7F149950097E7B5B41B390436497CD21,其中JSESSIONID是固定的。而這個Session ID在響應瀏覽器的時候會被存儲到Cookie中,從而被保存到瀏覽器中;
  3. 而後面的value值對應的則是給該客戶端新創建的session的ID;
  4. 當用戶再一次訪問Servlet時,請求C都會攜帶着Cookie中的SessionID去訪問;
  5. 服務器會根據這個Session ID去查看是否有對應的Session對象;
  6. 如果有就拿出來使用;如果沒有就創建一個Session(相當於用戶第一次訪問)。

3. HttpSession生命週期:

3.1 什麼時候創建HttpSession​​​​

  • (1). 對於JSP而言:   是否瀏覽器訪問服務端的任何一個JSP,服務器都會立即創建一個HttpSession對象呢? 不一定。   ①.若當前的JSP或Servlet,是客戶端訪問當前WEB應用的第一個資源,且JSP的page指令中的session屬性爲false時,服務器是不會爲JSP創建HttpSession對象的;   ②.若當前JSP不是客戶端訪問的WEB應用的第一個資源,且其他頁面已經創建了一個HttpSession對象,則服務器也不會爲當前JSP創建一個新的HttpSession對象,而是會把和當前會話關聯的那個HttpSession對象返回給當前的JSP頁面。

  • (2). 對於Servlet而言:   若Servlet是客戶端訪問的第一個WEB應用資源,只有調用了request.getSession()或request.getSession(true) 纔會創建HttpSession對象。

3.2 什麼時候銷燬HttpSession對象

  • (1). 直接調用HttpSession的invalidate()方法,會使HttpSession失效;

  • (2). 服務器卸載了當前Web應用;

  • (3). 超出了HttpSession的過期時間。

#代碼中設置session過期時間的方式
session.setMaxInactiveInterval(5);

#web.xml中設置session過期時間的方式
<session-config>
    <session-timeout>30</session-timeout>
</session-config>
  • 注:由於會有越來越多的用戶訪問服務器,因此Session也會越來越多。爲了防止內存溢出,服務器會把長時間內沒有活躍的Session從內存中刪除,而這個時間就是Session的超時時間。如果超過了超時時間沒訪問過服務器,Session就自動失效了。

4. Session的特點

1.Session數據保存在服務器端;
2.Session中可以保存任意類型的數據;
3.Session默認的生命週期是30分鐘,可以手動設置更長或更短的時間。

二. Cookie機制

1. 什麼是Cookie

Cookie翻譯成中文是甜餅的意思,其實就是一個小型的文本文件,用來保存一些簡單的信息(瀏覽器對Cookie的內存大小是有限制的)。Cookie由服務器端生成,並且會發送給 User-Agent (一般是瀏覽器),服務器一般會告訴瀏覽器設置一下Cookie,然後瀏覽器會自動將該 Cookie 以 key/value 的格式保存到瀏覽器的某個目錄下;等到下次請求同一網站時,瀏覽器會自動通過請求頭髮送該Cookie給服務器,前提是瀏覽器設置了啓用Cookie功能。

2. 爲什麼要有Cookie

Web應用程序是使用HTTP協議來傳輸數據的,而HTTP協議是無狀態的協議,也就是說一旦數據交換完畢,客戶端與服務器端的連接就會關閉,等再次交換數據就需要建立新的連接,這就意味着服務器無法從連接上跟蹤會話。比如我們登陸一個網站的時候,會提醒你要不要記住賬戶和密碼,這樣下次來你就不用再次輸入賬號密碼了,這就是Cookie的作用。當我們再次訪問的時候,服務器會直接根據我們的Cookie來獲取上一次取過的東西。

3. Cookie 的特點

3.1 Cookie 的過期時間

我們每次發送請求的時候,都會根據domain來設置相應的Cookie。Cookie有永久的,也有臨時的,每個瀏覽器都有自己的Cookie,我們可以通過設置expires、max-age來設置保存日期,如果不設置的話默認是臨時存儲,也就是說關閉瀏覽器後Cookie就會消失。 

document.cookie = 'expires=時間/max-age=秒'

3.2 Cookie要滿足同源策略

雖然網站news.baidu.comwww.baidu.com同屬於Baidu,但是域名卻不一樣,也就是說這兩者之間是不能互相操作彼此Cookie的。只有域名和path都必須一樣,才能相互訪問彼此的Cooki。但是需要注意不同瀏覽器對path訪問規定是不一樣的,對於chrome,path必須爲當前目錄,設置爲其他目錄無效,當前頁面只能訪問當前目錄的Cookie`。

3.3 Cookie內存大小受限制

Cookie有個數和大小的限制,大小一般是4k,但是不同的瀏覽器,具體的Cookie大小也是不同的。

  • Firefox和Safari允許Cookie多達4097個字節,包括名(name)、值(value)和等號;

  • Opera允許Cookie多達4096個字節,包括名(name)、值(value)和等號;

  • Internet Explorer允許Cookie多達4095個字節,包括名(name)、值(value)和等號。

3.4 Cookie的安全性

Cookie是保存在瀏覽器本地的,是可以被修改的,所以敏感的數據不要放在Cookie裏。

三. Session共享

1. Session存在的問題

HttpSession是通過Servlet容器創建和管理的,像Tomcat/Jetty都是保存在內存中的。但是如果我們將Web應用橫向擴展成分佈式的集羣,然後利用LVS或Nginx進行負載均衡,那麼對來自同一用戶的Http請求,將有可能被負載分發到兩個不同的服務器實例中去。那麼如何保證不同實例間的Session共享,就成爲一個不得不解決的問題。

最簡單的解決方法就是把Session數據保存到內存以外的一個統一的地方,例如Memcached/Redis中。那麼問題又來了,如何替換掉Servlet容器,來創建和管理HttpSession呢?

2. Session共享的實現方案

  1. 利用Servlet容器提供的插件功能,自定義HttpSession的創建和管理策略,並通過配置的方式替換掉默認的策略。不過這種方式有個缺點,就是需要耦合Tomcat/Jetty等Servlet容器的代碼。這方面其實早就有開源項目了,例如memcached-session-manager,以及tomcat-redis-session-manager,不過這暫時都只支持Tomcat6/Tomcat7。

  2. 配置Nginx的負載均衡算法爲ip_hash,這樣每個請求按訪問IP的hash結果分配,這樣來自同一個IP的訪客就會固定訪問一個後端服務器,有效解決了動態網頁存在的Session共享問題。

  3. 使用Shiro管理Session,可以用Redis來實現Shiro 的SessionDao接口,這樣Session便歸Redis來保存了。

  4. 設計一個Filter,利用HttpServletRequestWrapper,實現自己的 getSession()方法,來接管創建和管理Session數據的工作。Spring-Session就是通過這樣的思路實現的。

  5. Spring-Session結合Redis實現Session共享。

四. Spring Session

1. 傳統Session的問題

我們都知道Session是由Web容器管理的,即一個Session只保存在一臺機器上,適合於單體應用。但隨着架構的演練,不斷的向微服務分佈式集羣演進,此時傳統的Session就不能工作了。如:現在有3臺Web服務器,客戶端通過Nginx負載均衡技術,來負載到某一臺服務器上,用戶此次的數據就保存到了這臺服務器的Web容器中了。等用戶下次請求時,如果被負載到其它機器上,那麼就拿不到之前保存的數據了,這時候就需要整個服務器集羣共享同一個Session。

爲了解決所有服務器共享一個Session,那麼Session就不能單獨的保存在自己的Web容器中,而是要保存在一個公共的會話倉庫(Session Repository)中,也就是說所有的服務器都要訪問這同一個倉庫,這樣所有服務器的狀態便都一致了。Spring Session支持的倉庫有Reids、MongoDB、JDBC等。

2. Spring Session的存儲方式

默認情況下,在Spring Boot中,爲Spring Session提供了幾種存儲方式:

  • JDBC

  • MongoDB

  • Redis

  • Hazelcast

  • HashMap

在Spring Session可用的情況下,我們可以選擇存儲Session的存儲類型StoreType。例如,按如下配置將使用JDBC作爲後端存儲:

spring.session.store-type=jdbc

# 通過設置`store-type`爲`none`可以禁用Spring Session

:

出於向後兼容,在Redis可用的情況下,Spring Boot中會默認自動配置及使用Redis來存儲Spring Session。

另外每種存儲方式都有特殊設置,例如,對於jdbc存儲可自定義要存儲的表名:

spring.session.jdbc.table-name=SESSIONS

3. Spring Session的優點

Spring Session是基於Servlet規範實現的一套Session管理框架,主要解決了分佈式場景下的Session共享問題。Spring Session最核心的類是SessionRepositoryFilter過濾器,用於包裝用戶的請求和響應,我們可以在程序中直接替換掉HttpSession,而無需修改一行代碼。而且也可以很方便的與Spring Security集成,增加諸如findSessionsByUserName、rememberMe等功能,限制同一個賬號可以同時在線的Session數量(如設置成1,即可達到把前一次登錄頂掉的效果)等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章