由於 HTTP 協定是無狀態的,完成操作關閉瀏覽器後,使用者端和伺服器端的連線就斷開了,所以我們必須要有一種機制來保證使用者端和伺服器端之間對談的連續性,也稱為認證,最常見的應用場景就是保持使用者的登入態。
最基本的認證方式,就是使用 Sesson-Cookie。
以保持使用者登入態為例,Sesson-Cookie 認證的具體步驟如下:
1)使用者端(瀏覽器): 向伺服器傳送登入資訊(使用者名稱和密碼)來請求登入校驗;
2)伺服器端: 驗證登入資訊,驗證通過後伺服器(比如 Tomcat)會自動為此次請求開闢一塊記憶體空間(一個 Session 物件),可以手動將使用者資訊(比如登入保持時間是否過期)存在 Session 物件中。然後,伺服器會自動為這個 Sesson 物件生成一個唯一的標識 sessionID ,並在 HTTP 響應頭(Header)的 Set-Cookie:JSESSIONID=XXXXXXX
中設定這個 seesionID。
所以說,Session 的實現是依賴於 Cookie 的
3)使用者端: 收到伺服器端的響應後會解析響應頭,從而根據 set-Cookie
將 sessonId
儲存在本地 Cookie 中,這樣,使用者端(瀏覽器)在下次 HTTP 請求時請求頭會自動附上該域名下的 Cookie 資訊;
4)伺服器端: 接收使用者端請求時會去解析請求頭 Cookie 中的 sessonId
,然後根據這個 sessonId
去找 Sesson 物件,從而獲取到使用者資訊;
可以通過攔截器在每次請求前嘗試獲取 Sesson 物件:Session 存活期間,我們認為使用者端一直處於活躍狀態(使用者處於登入態),一旦 Session 超期過時,那麼就可以認為使用者端已經停止和伺服器進行互動了(使用者退出登入)。
如果遇到禁用 Cookie 的情況,一般的做法就是把這個 sessionID 放到 URL 引數中。這也是經常在面試中會被問到的問題。
可能會有同學問為啥不直接把資料全部存在 Cookie 中,還整個 Session 出來然後把 sessionID 存在 Cookie 中的?
- Cookie 長度的限制:首先,最基本的,Cookie 是有長度限制的,這限制了它能儲存的資料的長度
- 效能影響:Cookie 確實和 Session 一樣可以讓伺服器端程式跟蹤每個使用者端的存取,但是每次使用者端的存取都必須傳回這些 Cookie,那如果 Cookie 中儲存的資料比較多的話,這無疑增加了使用者端與伺服器端之間的資料傳輸量,增加了伺服器的壓力。
- 安全性:Session 資料其實是屬於伺服器端的資料,而 Cookie 屬於使用者端,把本應在 Session 中儲存的資料放到使用者端 Cookie,使得伺服器端資料延伸到了外部網路及使用者端,顯然是存在安全性上的問題的。當然我們可以對這些資料做加密,不過從技術來講物理上不接觸才是最安全的。
登入:
攔截器:每次請求前去找 Sesson 物件,從而獲取到使用者資訊
可以看出來,在一次對談當中,兩個請求獲取到的 Session 物件實際上是同一個物件。
上面已經提到,伺服器是根據 cookie 中的 sessionID 來找到 Session 物件的,但以上程式碼中我們只是手動將使用者資料設定到了 Session 中,並沒有出現任何關於 Cookie 的程式碼(將 SessionId 設定到 Cookie 中)
很明顯,這些肯定都是伺服器(比如 Tomcat)自動完成的了。在第一次獲取 Session 即呼叫 request.getSession()
的時候,伺服器會自動建立一個 Session 物件(Session 是一個集合,並且是一個 Map 集合),並且存入伺服器的 Session 集合中以 SessionId 為標識鍵,也就是說根據 SessionId 即可取到對應 Session 的參照。同時也會建立一個鍵名為 JSESSIONID 的 Cookie 並且返回給瀏覽器,該 Cookie 的值即為 SessionId。
這個儲存著 SessionId 的 Cookie 會跟著請求上傳到伺服器,所以說,在同一對談當中,不管哪個請求拿到的都是同一個 Session 物件。
這種機制在單體應用時代應用非常廣泛,但是,隨著分散式時代的到來,Session 的缺點也逐漸暴露出來。
舉個例子,比如我們有多個伺服器,使用者端 1 向伺服器傳送了一個請求,由於負載均衡的存在,該請求被轉發給了伺服器 A,於是伺服器 A 建立並儲存了這個 Session
緊接著,使用者端 1 又向伺服器傳送了一個請求,但是這一次請求被負載均衡給了伺服器 B,而伺服器 B 這時候是沒有儲存伺服器 A 的 Session 的,這就導致 Session 的失效。
明明使用者在上一個介面還是登入的,跳到下一個介面就退出登入了,這顯然不合理。
當然了,對此的解決方法其實也有很多種,其實就是如何解決 Session 在多個伺服器之間的共用問題:
這個是最容易想到的,既然伺服器 B 沒有伺服器 A 儲存的 Session,那各個伺服器之間同步一下 Session 資料不就完了。
這種方案存在的問題也是顯而易見的:
同步 Session 資料帶來了額外的網路頻寬開銷。只要 Session 資料有變化,就需要將資料同步到所有其他機器上,機器越多,同步帶來的網路頻寬開銷就越大。
每臺Web伺服器都要儲存所有 Session 資料,如果整個叢集的 Session 資料很多(比如很多人同時存取網站的情況),每臺伺服器用於儲存 Session 資料的記憶體佔用會非常嚴重。
從名稱也能看出來,Sticky,即讓負載均衡器能夠根據每次的請求的對談標識來進行請求的轉發,保證一個對談中的每次請求都能落到同一臺伺服器上面。
存在問題的:
如果某臺伺服器宕機或者重啟了,那麼它上面儲存的 Session 資料就丟失了,使用者就需要重新進行登陸。
負載均衡器變為一個有狀態的節點,因為他需要儲存 Session 到具體伺服器的對映,和之前無狀態的節點相比,記憶體消耗會更大,容災方面會更麻煩。
將每個伺服器的 Session 資料都集中存到外部媒介比如 Redis 或者 MySQL 中去,然後所有的伺服器都從這個外部媒介中拿 Session 就行了
存在的問題也很顯然:
小夥伴們大家好呀,本文首發於公眾號@飛天小牛肉,阿里雲 & InfoQ 簽約作者,分享大廠面試原創高質量題解、原創技術乾貨和成長經驗。回覆『春秋招』我拉你進求職吹水交流群,回覆『Echo』免費獲取社群專案手把手教學)