javascript
Spring Session - Cookie VS Session VS Token 以及 Session不一致问题的N种解决方案
文章目錄
- 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請求的時候,把這個字符串給一并捎過來, 這樣服務端就可以很好地區分了。
舉個例子試想一下, 兩個節點組成了一個集群, 用戶一通過節點A登錄了系統, 那session id會保存在節點A上,如果用戶一的下一次請求被轉發到節點B怎么辦?節點B可沒有用戶一的 session id 即相當于用戶一沒有登錄呀~
當然了可以通過一些辦法解決,比如ng上開啟 session sticky , 就是讓用戶一的請求一直粘連在節點A上 。 但是節點A掛掉了, 還得轉到節點B去。 那就是session 的復制唄, 把session id 在兩個節點之間同步(tomcat之間進行session復制) 。
隨著用戶數據量的激增 , 每個人只需要保存自己的session id,而服務器要保存所有人的session id , 對服務器說是一個巨大的開銷 , 嚴重的限制了服務器擴展能力 。
后來呢,大家說把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就隨之而來
主要流程如下:
優點
- 無狀態、可擴展
- 支持移動設備
- 跨程序調用
- 安全
好了,扯皮結束了,我們先關注分布式環境下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等
總結
以上是生活随笔為你收集整理的Spring Session - Cookie VS Session VS Token 以及 Session不一致问题的N种解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java - 探究前后分离带来的跨域问题
- 下一篇: Spring Session - 使用S