TODO: 該博文,兩處暫時不明白:java
1. 10.4.3最後的cookie被盜。難道sessionID被盜取以後,後面生成的session簽名不會再次被盜取?web
2. 表單重複提交問題。算法
轉自《深刻分析java Web》技術內幕 -- 許令波apache
Session 與 Cookie 的做用都是爲了保持訪問用戶與後端服務器的交互狀態。它們有各自的優勢,也有各自的缺陷,然而具備諷刺意味的是它們的優勢和它們的使用場景又是矛盾的。例如,使用 Cookie 來傳遞信息時,隨着 Cookie 個數的增多和訪問量的增長,它佔用的網絡帶寬也很大,試想假如 Cookie 佔用 200 個字節,若是一天的 PV 有幾億,它要佔用多少帶寬?因此有大訪問量的時候但願用 Session,可是 Session 的致命弱點是不容易在多臺服務器之間共享,因此這也限制了 Session 的使用。後端
Cookie 的做用我想你們都知道,通俗地說就是當一個用戶經過 HTTP 協議訪問一個服務器的時候,這個服務器會將一些 Key/Value 鍵值對返回給客戶端瀏覽器,並給這些數據加上一些限制條件,在條件符合時這個用戶下次訪問這個服務器的時候,數據又被完整地帶回給服務器。跨域
這個做用就像您去超市購物時,第一次給您辦張購物卡,這個購物卡里存放了一些您的我的信息,下次您再來這個連鎖超市時,超市會識別您的購物卡,下次直接購物就行了。瀏覽器
當初 W3C 在設計 Cookie 時實際上考慮的是爲了記錄用戶在一段時間內訪問 Web 應用的行爲路徑。因爲 HTTP 協議是一種無狀態協議,當用戶的一次訪問請求結束後,後端服務器就沒法知道下一次來訪問的仍是不是上次訪問的用戶,在設計應用程序時,咱們很容易想到兩次訪問是同一人訪問與不一樣的兩我的訪問對程序設計和性能來講有很大的不一樣。例如,在一個很短的時間內,若是與用戶相關的數據被頻繁訪問,能夠針對這個數據作緩存,這樣能夠大大提升數據的訪問性能。Cookie 的做用正是在此,因爲是同一個客戶端發出的請求,每次發出的請求都會帶有第一次訪問時服務端設置的信息,這樣服務端就能夠根據 Cookie 值來劃分訪問的用戶了。緩存
當前 Cookie 有兩個版本:Version 0 和 Version 1。經過它們有兩種設置響應頭的標識,分別是 「Set-Cookie」和「Set-Cookie2」。這兩個版本的屬性項有些不一樣,表 10-1 和表 10-2 是兩個版本的屬性介紹。安全
表 10-1.Version 0 屬性項介紹服務器
屬性項 | 屬性項介紹 |
---|---|
NAME=VALUE | 鍵值對,能夠設置要保存的 Key/Value,注意這裏的 NAME 不能和其餘屬性項的名字同樣 |
Expires | 過時時間,在設置的某個時間點後該 Cookie 就會失效,如 expires=Wednesday, 09-Nov-99 23:12:40 GMT |
Domain | 生成該 Cookie 的域名,如 domain="xulingbo.net" |
Path | 該 Cookie 是在當前的哪一個路徑下生成的,如 path=/wp-admin/ |
Secure | 若是設置了這個屬性,那麼只會在 SSH 鏈接時纔會回傳該 Cookie |
表 10-2.Version 1 屬性項介紹
屬 性 項 | 屬性項介紹 |
---|---|
NAME=VALUE | 與 Version 0 相同 |
Version | 經過 Set-Cookie2 設置的響應頭建立必須符合 RFC2965 規範,若是經過 Set-Cookie 響應頭設置,默認值爲 0,若是要設置爲 1,則該 Cookie 要遵循 RFC 2109 規範 |
Comment | 註釋項,用戶說明該 Cookie 有何用途 |
CommentURL | 服務器爲此 Cookie 提供的 URI 註釋 |
Discard | 是否在會話結束後丟棄該 Cookie 項,默認爲 fasle |
Domain | 相似於 Version 0 |
Max-Age | 最大失效時間,與 Version 0 不一樣的是這裏設置的是在多少秒後失效 |
Path | 相似於 Version 0 |
Port | 該 Cookie 在什麼端口下能夠回傳服務端,若是有多個端口,以逗號隔開,如 Port="80,81,8080" |
Secure | 相似於 Version 0 |
以上兩個版本的 Cookie 中設置的 Header 頭的標識符是不一樣的,咱們經常使用的是 Set-Cookie:userName=「junshan」; Domain=「xulingbo.net」,這是 Version 0 的形式。針對 Set-Cookie2 是這樣設置的:Set-Cookie2:userName=「junshan」; Domain=「xulingbo.net」; Max-Age=1000。可是在 Java Web 的 Servlet 規範中並不支持 Set-Cookie2 響應頭,在實際應用中 Set-Cookie2 的一些屬性項卻能夠設置在 Set-Cookie 中,如這樣設置:Set-Cookie:userName=「junshan」; Version=「1」;Domain=「xulingbo.net」;Max-Age=1000。
當咱們用以下方式建立 Cookie 時:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Cookie 是如何加到 HTTP 的 Header 中的?當咱們用 Servlet 3.0 規範來建立一個 Cookie 對象時,該 Cookie 既支持 Version 0 又支持 Version 1,若是您設置了 Version 1 中的配置項,即便您沒有設置版本號,Tomcat 在最後構建 HTTP 響應頭時也會自動將 Version 的版本設置爲 1。下面看一下 Tomcat 是如何調用 addCookie 方法,圖 10-1 是 Tomcat 建立 Set-Cookie 響應頭的時序圖。
圖 10-1.Tomcat 建立 Set-Cookie 響應頭的時序圖
從圖 10-1 中能夠看出,真正構建 Cookie 是在 org.apache.catalina.connector. Response 類中完成的,調用 generateCookieString 方法將 Cookie 對象構形成一個字符串,構造的字符串的格式如 userName=「junshan」;Version=「1」; Domain=「xulingbo.net」; Max-Age=1000。而後將這個字符串命名爲 Set-Cookie 添加到 MimeHeaders 中。
在這裏有幾點須要注意:
不知道您有沒有注意一個問題,就是當咱們經過 response.addCookie 建立多個 Cookie 時,這些 Cookie 最終是在一個 Header 項中仍是以獨立的 Header 存在的,通俗地說也就是咱們每次建立 Cookie 時是否都是建立一個以 NAME 爲 Set-Cookie 的 MimeHeaders ?答案是確定的。從上面的時序圖中能夠看出每次調用 addCookie 的時候,最終都會建立一個 Header,可是咱們還不知道最終在請求返回時構造 HTTP 響應頭是否將相同 Header 標識的 Set-Cookie 值進行合併。
咱們找到 Tomcat 最終構造 HTTP 響應頭的代碼,這段代碼位於 org.apache.coyote.http11. Http11Processor 類的 prepareResponse 方法中,以下所示:
1 2 3 4 |
|
這段代碼清楚地表示,在構建 HTTP 返回字節流時是將 Header 中全部的項順序地寫出,而沒有進行任何修改。因此能夠想象瀏覽器在接收 HTTP 協議返回的數據時是分別解析每個 Header 項的。
另外,目前不少工具均可以觀察甚至能夠修改瀏覽器中的 Cookie 數據。例如,在 Firefox 中能夠經過 HttpFox 插件來查看返回的 Cookie 數據,如圖 10-2 所示。
圖 10-2.HttpFox 插件展現的 Header 數據
在 cookie 項中能夠詳細查看 Cookie 屬性項,如圖 10-3 所示。
圖 10-3.HttpFox 插件展現的 Cookie 數據
前面主要介紹了服務端如何建立 Cookie,下面看一下如何從客戶端獲取 Cookie。
當咱們請求某個 URL 路徑時,瀏覽器會根據這個 URL 路徑將符合條件的 Cookie 放在 Request 請求頭中傳回給服務端,服務端經過 request.getCookies() 來取得全部 Cookie。
Cookie 是 HTTP 協議頭中的一個字段,雖然 HTTP 協議自己對這個字段並無多少限制,可是 Cookie 最終仍是存儲在瀏覽器裏,因此不一樣的瀏覽器對 Cookie 的存儲都有一些限制,表 10-3 是一些一般的瀏覽器對 Cookie 的大小和數量的限制。
表 10-3.瀏覽器對 Cookie 的大小和數量的限制
瀏覽器版本 | Cookie 數限制 | Cookie 總大小限制 |
---|---|---|
IE6 | 20 個 / 每一個域名 | 4095 個字節 |
IE7 | 50 個 / 每一個域名 | 4095 個字節 |
續表
瀏覽器版本 | Cookie 數限制 | Cookie 總大小限制 |
---|---|---|
IE8 | 50 個 / 每一個域名 | 4095 個字節 |
IE9 | 50 個 / 每一個域名 | 4095 個字節 |
Chrome | 50 個 / 每一個域名 | 大於 80000 |
FireFox | 50 個 / 每一個域名 | 4097 個字 |
前面已經介紹了 Cookie 可讓服務端程序跟蹤每一個客戶端的訪問,可是每次客戶端的訪問都必須傳回這些 Cookie,若是 Cookie 不少,這無形地增長了客戶端與服務端的數據傳輸量,而 Session 的出現正是爲了解決這個問題。
同一個客戶端每次和服務端交互時,不須要每次都傳回全部的 Cookie 值,而是隻要傳回一個 ID,這個 ID 是客戶端第一次訪問服務器的時候生成的,並且每一個客戶端是惟一的。這樣每一個客戶端就有了一個惟一的 ID,客戶端只要傳回這個 ID 就好了,這個 ID 一般是 NANE 爲 JSESIONID 的一個 Cookie。
下面詳細講一下 Session 如何基於 Cookie 來工做。實際上有三種方式能可讓 Session 正常工做:
第一種狀況下,當瀏覽器不支持 Cookie 功能時,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數中,它的傳遞格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中「Servlet;」後面的 K-V 就是要傳遞的 Path Parameters,服務器會從這個 Path Parameters 中拿到用戶配置的 SessionCookieName。關於這個 SessionCookieName,若是在 web.xml 中配置 session-config 配置項,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值。若是沒有配置了 session-config 配置項,默認的 SessionCookieName 就是你們熟悉的「JSESSIONID」。須要說明的一點是,與 Session 關聯的 Cookie 與其餘 Cookie 沒有什麼不一樣。接着 Request 根據這個 SessionCookieName 到 Parameters 中拿到 Session ID 並設置到 request.setRequestedSessionId 中。
請注意,若是客戶端也支持 Cookie,Tomcat 仍然會解析 Cookie 中的 Session ID,並會覆蓋 URL 中的 Session ID。
若是是第三種狀況,將會根據 javax.servlet.request.ssl_session 屬性值設置 Session ID。
有了 Session ID 服務端就能夠建立 HttpSession 對象了,第一次觸發經過 request.getSession() 方法。若是當前的 Session ID 尚未對應的 HttpSession 對象,那麼就建立一個新的,並將這個對象加到 org.apache.catalina. Manager 的 sessions 容器中保存。Manager 類將管理全部 Session 的生命週期,Session 過時將被回收,服務器關閉,Session 將被序列化到磁盤等。只要這個 HttpSession 對象存在,用戶就能夠根據 Session ID 來獲取這個對象,也就達到了狀態的保持。
Session 相關類圖如圖 10-4 所示。
圖 10-4.Session 相關類圖
從圖 10-4 中能夠看出,從 request.getSession 中獲取的 HttpSession 對象其實是 StandardSession 對象的門面對象,這與前面的 Request 和 Servlet 是同樣的原理。圖 10-5 是 Session 工做的時序圖。
圖 10-5.Session 工做的時序圖(查看大圖)
從時序圖中能夠看出,從 Request 中獲取的 Session 對象保存在 org.apache. catalina.Manager 類中,它的實現類是 org.apache.catalina.session.StandardManager,經過 requestedSessionId 從 StandardManager 的 sessions 集合中取出 StandardSession 對象。因爲一個 requestedSessionId 對應一個訪問的客戶端,因此一個客戶端,也就對應一個 StandardSession 對象,這個對象正是保存咱們建立的 Session 值的。下面咱們看一下 StandardManager 這個類是如何管理 StandardSession 的生命週期的。
圖 10-6.StandardManager 與 StandardSession 的類關係圖
StandardManager 類負責 Servlet 容器中全部的 StandardSession 對象的生命週期管理。當 Servlet 容器重啓或關閉時 StandardManager 負責持久化沒有過時的 StandardSession 對象,它會將全部的 StandardSession 對象持久化到一個以「SESSIONS.ser」爲文件名的文件中。到 Servlet 容器重啓時,也就是 StandardManager 初始化時,會從新讀取這個文件解析出全部 Session 對象,從新保存在 StandardManager 的 sessions 集合中。Session 恢復狀態圖如圖 10-7 所示。
圖 10-7.Session 恢復狀態圖
當 Servlet 容器關閉時 StandardManager 類會調用 unload 方法將 sessions 集合中的 StandardSession 對象寫到「SESSIONS.ser」文件中,而後在啓動時再按照上面的狀態圖從新恢復,注意要持久化保存 Servlet 容器中的 Session 對象,必須調用 Servlet 容器的 stop 和 start 命令,而不能直接結束(kill)Servlet 容器的進程,由於直接結束進程,Servlet 容器沒有機會調用 unload 方法來持久化這些 Session 對象。
另外,StandardManager 中的 sessions 集合中的 StandardSession 對象並非永遠保存的,不然 Servlet 容器的內存將很容易被消耗盡,因此必須給每一個 Session 對象定義一個有效時間,超過這個時間 Session 對象將被清除。在 Tomcat 中這個有效時間是 60(maxInactiveInterval 屬性控制)秒,超過 60 秒該 Session 將會過時。檢查每一個 Session 是否失效是在 Tomcat 的一個後臺線程中完成的(backgroundProcess() 方法中)。過時 Session 狀態圖如圖 10-8 所示。
圖 10-8.過時 Session 狀態圖
除了後臺進程檢查 Session 是否失效外,當調用 request.getSession() 時也會檢查該 Session 是否過時。值得注意的是,request.getSession() 方法調用的 StandardSession 永遠都會存在,即便與這個客戶端關聯的 Session 對象已通過期。若是過時,又會從新建立一個全新的 StandardSession 對象,可是之前設置的 Session 值將會丟失。若是您取到了 Session 對象可是經過 session.getAttribute 取不到前面設置的 Session 值,請不要奇怪,由於極可能它已經失效了,請檢查一下 <Manager pathname="" maxInactiveInterval="60" /> 中 maxInactiveInterval 配置項的值,若是不想讓 Session 過時能夠設置爲 -1。可是您要仔細評估一下,網站的訪問量和設置的 Session 的大小,防止將您的 Servlet 容器內存撐爆。若是不想自動建立 Session 對象,也能夠經過 request.getSession(boolean create) 方法來判斷該客戶端關聯的 Session 對象是否存在。
雖然 Cookie 和 Session 均可以跟蹤客戶端的訪問記錄,可是它們的工做方式顯然是不一樣的,Cookie 經過把全部要保存的數據經過 HTTP 協議的頭部從客戶端傳遞到服務端,又從服務端再傳回到客戶端,全部的數據都存儲在客戶端的瀏覽器裏,因此這些 Cookie 數據能夠被訪問到,就像咱們前面經過 Firefox 的插件 HttpFox 能夠看到全部的 Cookie 值。不只能夠查看 Cookie,甚至能夠經過 Firecookie 插件添加、修改 Cookie,因此 Cookie 的安全性受到了很大的挑戰。
相比較而言 Session 的安全性要高不少,由於 Session 是將數據保存在服務端,只是經過 Cookie 傳遞一個 SessionID 而已,因此 Session 更適合存儲用戶隱私和重要的數據。
從前面的分析可知,Session 和 Cookie 各自有優勢和缺點。在大型互聯網系統中,單獨使用 Cookie 和 Session 都是不可行的,緣由很簡單。由於若是使用 Cookie,能夠很好地解決應用的分佈式部署問題,大型互聯網應用系統一個應用有上百臺機器,並且有不少不一樣的應用系統協同工做,因爲 Cookie 是將值存儲在客戶端的瀏覽器裏,用戶每次訪問都會將最新的值帶回給處理該請求的服務器,因此也就解決了同一個用戶的請求可能不在同一臺服務器處理而致使的 Cookie 不一致的問題。
這種「誰家的孩子誰抱走」的處理方式的確是大型互聯網的一個比較簡單可是的確能夠解決問題的處理方式,可是這種處理方式也會帶來了不少其餘問題,如:
當以上問題不能再容忍下去的時候,就不得不想其餘辦法處理了。
既然 Cookie 有以上這些問題,Session 也有它的好處,爲什麼不結合使用 Session 和 Cookie 呢?下面是分佈式 Session 框架能夠解決的問題:
分佈式 Session 框架的架構圖如圖 10-9 所示。
爲了達成上面所說的幾點目標,咱們須要一個服務訂閱服務器,在應用啓動時能夠從這個訂閱服務器訂閱這個應用須要的可寫 Session 項和可寫 Cookie 項,這些配置的 Session 和 Cookie 能夠限制這個應用可以使用哪些 Session 和 Cookie,甚至能夠控制 Session 和 Cookie 可讀或者可寫。這樣能夠精確地控制哪些應用能夠操做哪些 Session 和 Cookie,能夠有效控制 Session 的安全性和 Cookie 的數量。
圖 10-9.Session 框架的架構圖
如 Session 的配置項能夠爲以下形式:
1 2 3 4 5 6 |
|
Cookie 的配置能夠爲以下形式:
1 2 3 4 5 6 7 8 9 |
|
統一經過訂閱服務器推送配置能夠有效地集中管理資源,因此能夠省去每一個應用都來配置 Cookie,簡化 Cookie 的管理。若是應用要使用一個新增 Cookie,能夠經過一個統一的平臺來申請,申請經過纔將這個配置項增長到訂閱服務器。若是是一個全部應用都要使用的全局 Cookie,那麼只需將這個 Cookie 經過訂閱服務器統一推送過去就好了,省去了要在每一個應用中手動增長 Cookie 的配置。
關於這個訂閱服務器如今有不少開源的配置服務器,如 Zookeeper 集羣管理服務器,能夠統一管理全部服務器的配置文件。
因爲應用是一個集羣,因此不可能將建立的 Session 都保存在每臺應用服務器的內存中,由於若是每臺服務器有幾十萬的訪問用戶,服務器的內存確定不夠用,即便內存夠用,這些 Session 也沒法同步到這個應用的全部服務器中。因此要共享這些 Session 必須將它們存儲在一個分佈式緩存中,能夠隨時寫入和讀取,並且性能要很好才能知足要求。當前能知足這個要求的系統有不少,如 MemCache 或者淘寶的開源分佈式緩存系統 Tair 都是很好的選擇。
解決了配置和存儲問題,下面看一下如何存取 Session 和 Cookie。
既然是一個分佈式 Session 的處理框架,必然會從新實現 HttpSession 的操做接口,使得應用操做 Session 的對象都是咱們實現的 InnerHttpSession 對象,這個操做必須在進入應用以前完成,因此能夠配置一個 filter 攔截用戶的請求。
先看一下如何封裝 HttpSession 對象和攔截請求,圖 10-10 是時序圖。
咱們能夠在應用的 web.xml 中配置一個 SessionFilter,用於在請求到達 MVC 框架以前封裝 HttpServletRequest 和 HttpServletResponse 對象,並建立咱們本身的 InnerHttpSession 對象,把它設置到 request 和 response 對象中。這樣應用系統經過 request.getHttpSession() 返回的就是咱們建立的 InnerHttpSession 對象了,咱們能夠攔截 response 的 addCookies 設置的 Cookie。
在時序圖中,應用建立的全部 Session 對象都會保存在 InnerHttpSession 對象中,當用戶的此次訪問請求完成時,Session 框架將會把這個 InnerHttpSession 的全部內容再更新到分佈式緩存中,以便於這個用戶經過其餘服務器再次訪問這個應用系統。另外,爲了保證一些應用對 Session 穩定性的特殊要求能夠將一些很是關鍵的 Session 再存儲到 Cookie 中,如當分佈式緩存存在問題時,能夠將部分 Session 存儲到 Cookie 中,這樣即便分佈式緩存出現問題也不會影響關鍵業務的正常運行。
圖 10-10.HttpSession 攔截請求時序圖(查看大圖)
還有一個很是重要的問題就是如何處理跨域名來共享 Cookie 的問題。咱們知道 Cookie 是有域名限制的,也就是一個域名下的 Cookie 不能被另外一個域名訪問,因此若是在一個域名下已經登陸成功,如何訪問到另一個域名的應用且保證登陸狀態仍然有效,這個問題大型網站應該常常會遇到。如何解決這個問題呢?下面介紹一種處理方式,如圖 10-11 所示。
圖 10-11.跨域名同步 session(查看大圖)
從圖中能夠看出,要實現 Session 同步,須要另一個跳轉應用,這個應用能夠被一個或者多個域名訪問,它的主要功能是從一個域名下取得 sessionID,而後將這個 sessionID 同步到另一個域名下。這個 sessionID 其實就是一個 Cookie,至關於咱們常常遇到的 JSESSIONID,因此要實現兩個域名下的 Session 同步,必需要將同一個 sessionID 做爲 Cookie 寫到兩個域名下。
總共 12 步,一個域名不用登陸就取到了另一個域名下的 Session,固然這中間有些步驟還能夠簡化,也能夠作一些額外的工做,如能夠寫一些須要的 Cookie,而不只僅只傳一個 sessionID。
除此以外,該框架還能處理 Cookie 被盜取的問題。如您的密碼沒有丟失,可是您的帳號卻有可能被別人登陸的狀況,這種狀況極可能就是由於您登陸成功後,您的 Cookie 被別人盜取了,盜取您的 Cookie 的人將您的 Cookie 加入到他的瀏覽器,而後他就能夠經過您的 Cookie 正常訪問您的我的信息了,這是一個很是嚴重的問題。在這個框架中咱們能夠設置一個 Session 簽名,當用戶登陸成功後咱們根據用戶的私密信息生成的一個簽名,以表示當前這個惟一的合法登陸狀態,而後將這個簽名做爲一個 Cookie 在當前這個用戶的瀏覽器進程中和服務器傳遞,用戶每次訪問服務器都會檢查這個簽名和從服務端分佈式緩存中取得的 Session 從新生成的簽名是否一致,若是不一致,顯然這個用戶的登陸狀態不合法,服務端將清除這個 sessionID 在分佈式緩存中的 Session 信息,讓用戶從新登陸。
Cookie 是在 HTTP 的頭部,因此一般的 gzip 和 deflate 針對 HTTP Body 的壓縮不能壓縮 Cookie,若是 Cookie 量很是大,能夠考慮將 Cookie 也作壓縮,壓縮方式是將 Cookie 的多個 k/v 對當作普通的文本,作文本壓縮。壓縮算法一樣可使用 gzip 和 deflate 算法,可是須要注意的一點是,根據 Cookie 的規範,Cookie 中不能包含控制字符,僅僅只能包含 ASCII 碼爲(34 ~ 126)的可見字符。因此要將壓縮後的結果再進行轉碼,能夠進行 Base32 或者 Base64 編碼。
能夠配置一個 Filter 在頁面輸出時對 Cookie 進行所有或者部分壓縮,以下代碼所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
上面的代碼是用 DeflaterOutputStream 對 Cookie 進行壓縮的,Deflater 壓縮後再進行 BASE64 編碼,相應地用 InflaterInputStream 進行解壓。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
2KB 大小的 Cookie 壓縮前與壓縮後字節數相差 20% 左右,若是您的網站的 Cookie 在 2KB~3KB 左右,一天有 1 億的 PV,那麼一天就可以產生 4TB 的帶寬流量了,從節省帶寬成原本說壓縮仍是頗有必要的。
網站中在不少地方都有表單重複提交問題,一種狀況是用戶在網速慢的狀況下可能會重複提交表單,還有就是惡意用戶經過程序來發送惡意請求,在這些狀況下都要設計一個防止表單重複提交的機制。
要可以防止表單重複提交,就要標識用戶的每一次訪問請求,使得每一次訪問對服務端來講都是惟一肯定的。爲了標識用戶的每次訪問請求,能夠在用戶請求一個表單域時增長一個隱藏表單項,這個表單項的值每次都是惟一的 token,如:
1 2 3 |
|
當用戶在請求時生成這個惟一的 token 時,同時將這個 token 保存在用戶的 Session 中,等用戶提交請求時檢查這個 token 和當前的 Session 中保存的 token 是否一致。若是一致,說明沒有重複提交,不然用戶提交上來的 token 已經不是當前的這個請求的合法 token。其工做過程如圖 10-12 所示。
圖 10-12.工做過程
圖 10-12 是用戶發起對錶單頁面的請求過程,生成惟一的 token 須要一個算法,最簡單的就是能夠根據一個種子做爲 key 生成一個隨機數,並保存在 Session 中,等下次用戶提交表單時作驗證。驗證表單的過程如圖 10-13 所示。
圖 10-13.驗證表單的過程
當用戶提交表單時會將請求時生成的 token 帶回來,這樣就能夠和 Session 中保存的 token 作對比,從而確認此次表單驗證是否合法。
Cookie 和 Session 都是爲了保持用戶訪問的連續狀態,之因此要保持這種狀態,一方面是爲了方便業務實現,另外一方面就是簡化服務端程序設計,提升訪問性能,可是這也帶來了另一些挑戰,如安全問題、應用的分佈式部署帶來的 Session 的同步問題及跨域名 Session 的同步等一系列問題。本章分析了 Cookie 和 Session 的工做原理,並介紹了一致分佈式 Session 的解決方案。