文章目錄
- Cookie VS Session VS Token
- History
- Cookie
- Session
- Token
- Session不一致問題
- Session不一致解決方案
- nginx session sticky
- Tomcat session 復制
- Session 外部化存盤
Cookie VS Session VS Token
我們在學習Spring Session 之前, 先聊聊 幾種主流的會話方式以及發展歷史
History
眾所周知 HTTP請求是無狀態的, 隨著互動式Web應用的興起,要管理會話,那必須記住哪些人登錄了系統, 怎么辦呢?
大家就想著頒發一個會話標識(session id), 實際上呢就是一個隨機字串,每個人收到的都不一樣 ,
這樣每次向系統發起HTTP請求的時候,把這個字串給一并捎過來, 這樣服務端就可以很好地區分了,
隨著用戶資料量的激增 , 每個人只需要保存自己的session id,而服務器要保存所有人的session id , 對服務器說是一個巨大的開銷 , 嚴重的限制了服務器擴展能力 ,
舉個例子試想一下, 兩個節點組成了一個集群, 用戶一通過節點A登錄了系統, 那session id會保存在節點A上,如果用戶一的下一次請求被轉發到節點B怎么辦?節點B可沒有用戶一的 session id 即相當于用戶一沒有登錄呀~
當然了可以通過一些辦法解決,比如ng上開啟 session sticky , 就是讓用戶一的請求一直粘連在節點A上 , 但是節點A掛掉了, 還得轉到節點B去, 那就是session 的復制唄, 把session id 在兩個節點之間同步(tomcat之間進行session復制) ,

后來呢,大家說把session放到外面來管理吧 ,這樣就不用復制來復制去了 ,資料量大了還影響帶寬,,,,

艾瑪 ,你這個存盤session的,還是個單點呀,那我還得確保你這個節點高可用啊,,,,服務端想想說 ,我不管這些破session行不行 ,讓你客戶端去管你自己的這些資料呀?
又進化出了一版本 TOKEN
可是如果不保存這些session id , 怎么驗證客戶端發給服務端的session id 的確是服務端生成的呢? 如果不去驗證, 都不知道他們是不是合法登錄的用戶, 那…為所欲為了,
so , 重點來了, 服務端驗證合法性
舉個例子,用戶一已經登錄了系統,服務端給用戶一發一個令牌(token), 里邊包含了用戶一的 user id等資訊, 下一次用戶已再次通過Http 請求訪問服務端的時候, 把這個token 通過Http header 帶過來 就可以了,
等等, 那別人偽造怎么辦? 怎么讓別人偽造不了呢?
資料簽名
比如哈, 管理端用HMAC-SHA256 演算法,加上一個只有管理端自己才知道的密鑰, 對資料做一個簽名, 把這個簽名和資料一起作為token , 由于密鑰別人不知道, 就無法偽造token了,

token 管理端 不保存, 當用戶把這個token 發過來的時候,管理端再用同樣的HMAC-SHA256 演算法和同樣的密鑰,對資料再計算一次簽名, 和token 中的簽名做個比較, 如果相同, 這認為已經登錄過了,并且可以直接取到存盤在其中的的user id , 如果不相同, 資料部分肯定被人篡改過, 即為沒有認證,

Token 中的資料是明文保存的(雖然會用Base64做下編碼, 但不是加密), 還是可以被別人看到的, 所以Token中不能在其中保存像密碼等敏感資訊,
當然, 如果一個人的token 被別人偷走了,那其他用戶使用該token登錄 也會被認為合法用戶, 這其實和一個人的session id 被別人偷走道理是一樣的 , 只能放篡改,不能放泄露
這樣一來, 管理端就不保存session id 了, 只負責生成token , 然后驗證token ,消除了session id 這個負擔, 那么管理端的集群現在可以輕松地做水平擴展, 用戶訪問量激增, 加節點…

Cookie
cookie 指的是瀏覽器里面能永久存盤的一種資料,僅僅是瀏覽器實作的一種資料存盤功能,
cookie由服務器生成,發送給瀏覽器,瀏覽器把cookie以kv形式保存到某個目錄下的文本檔案內,下一次請求同一網站時會把該cookie發送給服務器,
由于cookie是存在客戶端上的,所以瀏覽器加入了一些限制確保cookie不會被惡意使用,同時不會占據太多磁盤空間,所以每個域的cookie數量是有限的,
Session
session 簡單來說就是服務器給每個客戶端分配的“身份標識”,然后客戶端每次向服務器發請求的時候,都帶上這個“身份標識”,服務器就知道這個請求來自于誰了,
至于客戶端怎么保存這個“身份標識”,可以有很多種方式,對于瀏覽器客戶端,大家都默認采用 cookie 的方式,
服務器使用session把用戶的資訊臨時保存在了服務器上,用戶退出后session會被銷毀,這種用戶資訊存盤方式相對cookie來說更安全.
Token
為什么非要用token呢?
我們都是知道HTTP協議是無狀態的,這種無狀態意味著程式需要驗證每一次請求,從而辨別客戶端的身份,
在這之前,程式都是通過在服務端存盤的登錄資訊來辨別請求的,這種方式一般都是通過存盤Session來完成,
隨著Web,應用程式以及移動端的興起,這種驗證的方式逐漸暴露出了問題,尤其是在可擴展性方面,
主要存在一下幾個問題
- Seesion: 每次認證用戶發起請求時,服務器需要去創建一個記錄來存盤資訊,當越來越多的用戶發請求時,記憶體的開銷也會不斷增加,
- 可擴展性: 在服務端的記憶體中使用Seesion存盤登錄資訊,伴隨而來的是可擴展性問題,
- CORS(跨域資源共享): 當我們需要讓資料跨多臺移動設備上使用時,跨域資源的共享會是一個讓人頭疼的問題,在使用Ajax抓取另一個域的資源,就可以會出現禁止請求的情況,
- CSRF(跨站請求偽造): 用戶在訪問銀行網站時,他們很容易受到跨站請求偽造的攻擊,并且能夠被利用其訪問其他的網站,
在這些問題中,可擴展行是最突出的,因此有必要去尋求一種更有行之有效的方法, TOKEN就隨之而來
主要流程如下:

- 用戶通過用戶名和密碼發送請求,
- 程式驗證,
- 程式回傳一個簽名的token 給客戶端,
- 客戶端儲存token,并且每次用于每次發送請求,
- 服務端驗證token并回傳資料,
優點
- 無狀態、可擴展
- 支持移動設備
- 跨程式呼叫
- 安全
好了,扯皮結束了,我們先關注分布式環境下Session的解決方案, 至于Token我會結合JWT來分享 ,

Session不一致問題
假設我們的應用部署在Tomcat中
【單個節點的tomcat 】
瀏覽器在第一次訪問服務器Tomcat1時,發現請求的 Cookie 中不存在 sessionid ,所以創建一個 sessionid 為 xxxxxxx 的 Session ,同時將該 sessionid 寫回給瀏覽器的 Cookie 中,
瀏覽器在下一次訪問 Web 服務器 時,Tomcat1會發現請求的 Cookie 中已存在 sessionid 為 xxxxxxx ,則直接獲得 xxxxxxx 對應的 Session ,
【多個節點的tomcat 】
在多臺 Tomcat 的情況下,采用 Nginx 做負載均衡,
接上面的請求,繼續 瀏覽器又發起一次請求訪問 Web 服務器,Nginx 負載均衡轉發請求到 Tomcat2 上,Tomcat2 會發現請求的 Cookie 中已存在 sessionid 為 X ,則直接獲得 xxxxxxx 對應的 Session ,結果 Tomcat2 在JVM中找不到 xxxxxxx 對應的 Session
這樣就會出現 Session 不一致的問題 ,
Session不一致解決方案
nginx session sticky
使用 Nginx 實作會話黏連,將相同 sessionid 的瀏覽器所發起的請求,轉發到同一臺服務器,這樣,就不會存在多個 Web 服務器創建多個 Session 的情況,也就不會發生 Session 不一致的問題,
不過,這種方式目前基本不被采用, 如果一臺服務器重啟,那么會導致轉發到這個服務器上的 Session 全部丟失,
主要是安裝 nginx-sticky-module
下載地址: https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/get/master.tar.gz
# tar zxf nginx-goodies-nginx-sticky-module-ng-1e96371de59f.tar.gz
# mv nginx-goodies-nginx-sticky-module-ng-1e96371de59f nginx-sticky
# tar zxf nginx-1.6.1.tar.gz
# cd nginx-1.6.1
# ./configure --prefix=/app/nginx --with-http_gzip_static_module --with-http_flv_module --with-http_dav_module --with-http_stub_status_module --with-http_realip_module --add-module=/app/soft/nginx-sticky/
# make
# cd /app/nginx/sbin
# mv nginx nginx.old
# cp /app/soft/nginx-1.6.1/objs/nginx ./
# cd -
# make upgrade
配置
upstream artisan{
sticky;
server 172.168.15.11:8001;
server 172.168.15.12:8002;
....
}
server {
listen 80;
server_name localhost;
.....
location ~/xxxxx/.*\.jsp|do|htm$ {
proxy_pass http://artisan;
.....
}
}
不用第三方的模塊包的話,那就使用ip_hash的策略,這種方案的局限性是ip不能變,
Tomcat session 復制
Web 服務器之間,進行 Session 復制同步,僅僅適用于實作 Session 復制的 Web 容器,例如 Tomcat
不過,這種方式目前基本也不被采用, session資料量大的時候,復制效率低,占用帶寬等等弊端,
Session 外部化存盤
Session 外部化存盤 即將 Session 存盤外部化,持久化到 MySQL、Redis、MongoDB 等中,這樣一搞Tomcat 就可以無狀態化,專注作為Web 服務 ,擴容也變得容易,
主要由兩種方式
- 方式一:基于 Tomcat、Jetty 等 Web 容器自帶的拓展,使用讀取外部存盤器的 Session 管理器 ,使用的較少,這里不做討論
基于Tomcat的tomcat-redis-session-manager插件,基于Jetty的jetty-session-redis插件、memcached-session-manager插件;
好處是對專案來說是透明的,無需改動代碼,但是由于過于依賴容器,一旦容器升級或者更換意味著又得重新配置;其實底層是,復制session到其它服務器,所以會有一定的延遲,也不能部署太多的服務器,
網上找了兩篇文章,感興趣的可以參考下
Tomcat會話管理器(Tomcat Session Manager)
Jetty集群配置Session存盤到MySQL、MongoDB
-
方式二:基于應用層封裝 HttpServletRequest 請求物件,包裝成自己的 RequestWrapper 物件,從而讓實作呼叫 HttpServletRequest#getSession() 方法時,獲得讀寫外部存盤器的 SessionWrapper 物件 , 比如 Spring Session解決方案
使用Spring session框架提供的會話管理工具, 這個方案既不依賴tomcat容器,又不需要改動代碼, 是目前非常完美的session共享解決方案,
我們這里只討論 Spring Session提供的解決方案 ,支持外部存盤包括 Redis . 資料庫、Hazelcast、MongoDB等


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/259982.html
標籤:其他
上一篇:多執行緒之初始并發問題:模擬搶票,使用執行緒鎖解決方案
下一篇:淺談安全基線
