30s 看懂基礎的認證方式: Session-Cookie 認證
?引言
由于 HTTP 協議是無狀態的,完成操作關閉瀏覽器后,客戶端和服務端的連接就斷開了,所以我們必須要有一種機制來保證客戶端和服務端之間會話的連續性,也稱為認證,最常見的應用場景就是保持用戶的登錄態。
最基本的認證方式,就是使用 Sesson-Cookie。
30s 圖解 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-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 對象。
Sesson-Cookie 認證的缺點與解決方案
這種機制在單體應用時代應用非常廣泛,但是,隨著分布式時代的到來,Session 的缺點也逐漸暴露出來。
舉個例子,比如我們有多個服務器,客戶端 1 向服務器發送了一個請求,由于負載均衡的存在,該請求被轉發給了服務器 A,于是服務器 A 創建并存儲了這個 Session
緊接著,客戶端 1 又向服務器發送了一個請求,但是這一次請求被負載均衡給了服務器 B,而服務器 B 這時候是沒有存儲服務器 A 的 Session 的,這就導致 Session 的失效。
明明用戶在上一個界面還是登錄的,跳到下一個界面就退出登錄了,這顯然不合理。
當然了,對此的解決方法其實也有很多種,其實就是如何解決 Session 在多個服務器之間的共享問題:
Sesson Replication
Sesson Sticky
Sesson 數據集中存儲
Session Replication
這個是最容易想到的,既然服務器 B 沒有服務器 A 存儲的 Session,那各個服務器之間同步一下 Session 數據不就完了。
這種方案存在的問題也是顯而易見的:
同步 Session 數據帶來了額外的網絡帶寬開銷。只要 Session 數據有變化,就需要將數據同步到所有其他機器上,機器越多,同步帶來的網絡帶寬開銷就越大。
每臺Web服務器都要保存所有 Session 數據,如果整個集群的 Session 數據很多(比如很多人同時訪問網站的情況),每臺服務器用于保存 Session 數據的內存占用會非常嚴重。
Session Sticky
從名稱也能看出來,Sticky,即讓負載均衡器能夠根據每次的請求的會話標識來進行請求的轉發,保證一個會話中的每次請求都能落到同一臺服務器上面。
存在問題的:
如果某臺服務器宕機或者重啟了,那么它上面存儲的 Session 數據就丟失了,用戶就需要重新進行登陸。
負載均衡器變為一個有狀態的節點,因為他需要保存 Session 到具體服務器的映射,和之前無狀態的節點相比,內存消耗會更大,容災方面會更麻煩。
Session 數據集中存儲
將每個服務器的 Session 數據都集中存到外部介質比如 Redis 或者 MySQL 中去,然后所有的服務器都從這個外部介質中拿 Session 就行了
存在的問題也很顯然:
過度依賴外部存儲,如果集中存儲 Session 的外部存儲機器出問題了,就會直接影響到我們的應用