30s 看懂基礎(chǔ)的認證方式: Session-Cookie 認證
?引言
由于 HTTP 協(xié)議是無狀態(tài)的,完成操作關(guān)閉瀏覽器后,客戶端和服務(wù)端的連接就斷開了,所以我們必須要有一種機制來保證客戶端和服務(wù)端之間會話的連續(xù)性,也稱為認證,最常見的應(yīng)用場景就是保持用戶的登錄態(tài)。
最基本的認證方式,就是使用 Sesson-Cookie。
30s 圖解 Sesson-Cookie 認證
以保持用戶登錄態(tài)為例,Sesson-Cookie 認證的具體步驟如下:
1)客戶端(瀏覽器): 向服務(wù)器發(fā)送登錄信息(用戶名和密碼)來請求登錄校驗;
2)服務(wù)端: 驗證登錄信息,驗證通過后服務(wù)器(比如 Tomcat)會自動為此次請求開辟一塊內(nèi)存空間(一個 Session 對象),可以手動將用戶信息(比如登錄保持時間是否過期)存在 Session 對象中。然后,服務(wù)器會自動為這個 Sesson 對象生成一個唯一的標識 sessionID ,并在 HTTP 響應(yīng)頭(Header)的 Set-Cookie:JSESSIONID=XXXXXXX 中設(shè)置這個 seesionID。
所以說,Session 的實現(xiàn)是依賴于 Cookie 的
3)客戶端: 收到服務(wù)端的響應(yīng)后會解析響應(yīng)頭,從而根據(jù) set-Cookie? 將 sessonId 保存在本地 Cookie 中,這樣,客戶端(瀏覽器)在下次 HTTP 請求時請求頭會自動附上該域名下的 Cookie 信息;
4)服務(wù)端: 接收客戶端請求時會去解析請求頭 Cookie 中的 sessonId?,然后根據(jù)這個 sessonId 去找 Sesson 對象,從而獲取到用戶信息;
可以通過攔截器在每次請求前嘗試獲取 Sesson 對象:Session 存活期間,我們認為客戶端一直處于活躍狀態(tài)(用戶處于登錄態(tài)),一旦 Session 超期過時,那么就可以認為客戶端已經(jīng)停止和服務(wù)器進行交互了(用戶退出登錄)。
如果遇到禁用 Cookie 的情況,一般的做法就是把這個 sessionID 放到 URL 參數(shù)中。這也是經(jīng)常在面試中會被問到的問題。
可能會有同學問為啥不直接把數(shù)據(jù)全部存在 Cookie 中,還整個 Session 出來然后把 sessionID 存在 Cookie 中的?
Cookie 長度的限制:首先,最基本的,Cookie 是有長度限制的,這限制了它能存儲的數(shù)據(jù)的長度
性能影響:Cookie 確實和 Session 一樣可以讓服務(wù)端程序跟蹤每個客戶端的訪問,但是每次客戶端的訪問都必須傳回這些 Cookie,那如果 Cookie 中存儲的數(shù)據(jù)比較多的話,這無疑增加了客戶端與服務(wù)端之間的數(shù)據(jù)傳輸量,增加了服務(wù)器的壓力。
安全性:Session 數(shù)據(jù)其實是屬于服務(wù)端的數(shù)據(jù),而 Cookie 屬于客戶端,把本應(yīng)在 Session 中存儲的數(shù)據(jù)放到客戶端 Cookie,使得服務(wù)端數(shù)據(jù)延伸到了外部網(wǎng)絡(luò)及客戶端,顯然是存在安全性上的問題的。當然我們可以對這些數(shù)據(jù)做加密,不過從技術(shù)來講物理上不接觸才是最安全的。
附加閱讀
Sesson-Cookie 認證偽代碼
登錄:
攔截器:每次請求前去找 Sesson 對象,從而獲取到用戶信息
可以看出來,在一次會話當中,兩個請求獲取到的 Session 對象實際上是同一個對象。
上面已經(jīng)提到,服務(wù)器是根據(jù) cookie 中的 sessionID 來找到 Session 對象的,但以上代碼中我們只是手動將用戶數(shù)據(jù)設(shè)置到了 Session 中,并沒有出現(xiàn)任何關(guān)于 Cookie 的代碼(將 SessionId 設(shè)置到 Cookie 中)
很明顯,這些肯定都是服務(wù)器(比如 Tomcat)自動完成的了。在第一次獲取 Session 即調(diào)用 request.getSession() 的時候,服務(wù)器會自動創(chuàng)建一個 Session 對象(Session 是一個集合,并且是一個 Map 集合),并且存入服務(wù)器的 Session 集合中以 SessionId 為標識鍵,也就是說根據(jù) SessionId 即可取到對應(yīng) Session 的引用。同時也會創(chuàng)建一個鍵名為 JSESSIONID 的 Cookie 并且返回給瀏覽器,該 Cookie 的值即為 SessionId。
這個存儲著 SessionId 的 Cookie 會跟著請求上傳到服務(wù)器,所以說,在同一會話當中,不管哪個請求拿到的都是同一個 Session 對象。
Sesson-Cookie 認證的缺點與解決方案
這種機制在單體應(yīng)用時代應(yīng)用非常廣泛,但是,隨著分布式時代的到來,Session 的缺點也逐漸暴露出來。
舉個例子,比如我們有多個服務(wù)器,客戶端 1 向服務(wù)器發(fā)送了一個請求,由于負載均衡的存在,該請求被轉(zhuǎn)發(fā)給了服務(wù)器 A,于是服務(wù)器 A 創(chuàng)建并存儲了這個 Session
緊接著,客戶端 1 又向服務(wù)器發(fā)送了一個請求,但是這一次請求被負載均衡給了服務(wù)器 B,而服務(wù)器 B 這時候是沒有存儲服務(wù)器 A 的 Session 的,這就導(dǎo)致 Session 的失效。
明明用戶在上一個界面還是登錄的,跳到下一個界面就退出登錄了,這顯然不合理。
當然了,對此的解決方法其實也有很多種,其實就是如何解決 Session 在多個服務(wù)器之間的共享問題:
Sesson Replication
Sesson Sticky
Sesson 數(shù)據(jù)集中存儲
Session Replication
這個是最容易想到的,既然服務(wù)器 B 沒有服務(wù)器 A 存儲的 Session,那各個服務(wù)器之間同步一下 Session 數(shù)據(jù)不就完了。
這種方案存在的問題也是顯而易見的:
同步 Session 數(shù)據(jù)帶來了額外的網(wǎng)絡(luò)帶寬開銷。只要 Session 數(shù)據(jù)有變化,就需要將數(shù)據(jù)同步到所有其他機器上,機器越多,同步帶來的網(wǎng)絡(luò)帶寬開銷就越大。
每臺Web服務(wù)器都要保存所有 Session 數(shù)據(jù),如果整個集群的 Session 數(shù)據(jù)很多(比如很多人同時訪問網(wǎng)站的情況),每臺服務(wù)器用于保存 Session 數(shù)據(jù)的內(nèi)存占用會非常嚴重。
Session Sticky
從名稱也能看出來,Sticky,即讓負載均衡器能夠根據(jù)每次的請求的會話標識來進行請求的轉(zhuǎn)發(fā),保證一個會話中的每次請求都能落到同一臺服務(wù)器上面。
存在問題的:
如果某臺服務(wù)器宕機或者重啟了,那么它上面存儲的 Session 數(shù)據(jù)就丟失了,用戶就需要重新進行登陸。
負載均衡器變?yōu)橐粋€有狀態(tài)的節(jié)點,因為他需要保存 Session 到具體服務(wù)器的映射,和之前無狀態(tài)的節(jié)點相比,內(nèi)存消耗會更大,容災(zāi)方面會更麻煩。
Session 數(shù)據(jù)集中存儲
將每個服務(wù)器的 Session 數(shù)據(jù)都集中存到外部介質(zhì)比如 Redis 或者 MySQL 中去,然后所有的服務(wù)器都從這個外部介質(zhì)中拿 Session 就行了
存在的問題也很顯然:
過度依賴外部存儲,如果集中存儲 Session 的外部存儲機器出問題了,就會直接影響到我們的應(yīng)用