cookie和session 詳析

一、    基本概念

Cookie和Session都是爲了保存用戶與後端服務器的交互狀態,基本來說Cookie是存在客戶端瀏覽器中,每次訪問服務器都帶着,HTTP請求字段中就有一個Cookie字段攜帶信息;而Session是存在服務器端的,每次用戶攜帶一個SID過來服務器就知道是誰了。

問題:HTTP對Cookie的大小是有限制的,假如一天的PV有幾個億,每個Cookie幾百個字節,那就大大的佔用了網絡帶寬;而Session在分佈式集羣時如何保持同步也是一個難題?

 

面試Cookie和Session區別:

1、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。

2、cookie不是很安全,別人可以分析存放在本地的COOKIE並進行COOKIE欺騙
   考慮到安全應當使用session。

3、session會在一定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能
   考慮到減輕服務器性能方面,應當使用COOKIE。

4、單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。

5、所以個人建議:
   將登陸信息等重要信息存放爲SESSION
   其他信息如果需要保留,可以放在COOKIE中

二、    Cookie總結

2.1 基本格式

  • NAME=VALUE:鍵值對,設置要保存的key/value
  • Expires:過期時間,如果不設置過期時間則表示會話Cookie,生命週期是瀏覽器會話器件,保存在內存中,瀏覽器關閉則消失;否則保存在硬盤上,可以在不同的瀏覽器進程間共享;
  • Domain:生成該Cookie的域名,如:domain=”xunbo.net”;
  • Path:該Cookie是當前那個路徑下生成的,比如:path=/wp-admin/;
  • Secure:如果設置了該屬性,那麼只有在SSH連接時纔會返回該Cookie;
  • Comment:註釋;
  • Port:該Cookie在什麼端口下可以回傳服務端,多個端口以逗號隔開;

2.2 HTTP協議和Cookie相關的字段

  • HTTP請求頭:Cookie字段攜帶保存的Cookie信息發送到服務器端
  • HTTP響應頭:Set-Cookie字段攜帶Cookie信息,例如: Set-Cookie: sc=4c31523a; path=/; domain=.acookie.taobao.com

2.3 Cookie大小限制

         雖然HTTP本身並沒有限制Cookie大小,但是瀏覽器考慮到內存佔用和網絡帶寬給出了Cookie大小限制;而且Cookie太長,有可能SID衝沒了,導致Session丟失。

以下數據不準確,各個博客給出的不一致:和瀏覽器版本有關

  • FireFox瀏覽器:每個域名最多50個;Cookie總大小4097個字節;
  • Chrome瀏覽器:每個域名最多50個;Cookie總大小4097個字節;

自己在前端用JS寫一個函數:

 

 

 2.4 Servlet中如何寫Cookie

 

注意:

  • 創建的Cookie的Name不能和屬性值相同,否則會拋出異常;
  • 創建的Cookie的NAME和Value都必須是ASCII字符,如果要支持中文,必須先URLEncoder將其編碼,否則會拋出異常;
  • NAME 和 VALUE 的值出現一些 TOKEN 字符(如“\”、“,”等)時,構建返回頭會將該 Cookie 的 Version 自動設置爲 1。
  • 注意每個addCookie方法會給HTTP響應頭中創建一個Set-Cookie,所以最終會有多個;

 

2.5 Cookie的安全性問題

雖然 Cookie 和 Session 都可以跟蹤客戶端的訪問記錄,但是它們的工作方式顯然是不同的,Cookie 通過把所有要保存的數據通過 HTTP協議的頭部從客戶端傳遞到服務端,又從服務端再傳回到客戶端,所有的數據都存儲在客戶端的瀏覽器裏,所以這些 Cookie 數據可以被訪問到,就像我們前面通過 Firefox 的插件 HttpFox 可以看到所有的 Cookie 值。不僅可以查看 Cookie,甚至可以通過 Firecookie 插件添加、修改 Cookie,所以 Cookie 的安全性受到了很大的挑戰。

相比較而言 Session 的安全性要高很多,因爲 Session 是將數據保存在服務端,只是通過 Cookie 傳遞一個 SessionID 而已,所以Session 更適合存儲用戶隱私和重要的數據。

 

2.6 Cookie壓縮問題

Cookie 是在 HTTP 的頭部,所以通常的 gzip 和 deflate 針對 HTTP Body 的壓縮不能壓縮 Cookie,如果 Cookie 量非常大,可以考慮將 Cookie 也做壓縮,壓縮方式是將 Cookie 的多個 k/v 對看成普通的文本,做文本壓縮。

壓縮算法同樣可以使用 gzip 和 deflate 算法,但是需要注意的一點是,根據 Cookie 的規範,Cookie 中不能包含控制字符,僅僅只能包含 ASCII 碼爲(34 ~ 126)的可見字符。所以要將壓縮後的結果再進行轉碼,可以進行 Base32 或者 Base64 編碼。

可以配置一個 Filter 在頁面輸出時對 Cookie 進行全部或者部分壓縮。

 

上面的代碼是用 DeflaterOutputStream 對 Cookie 進行壓縮的,Deflater 壓縮後再進行 BASE64 編碼,相應地用InflaterInputStream 進行解壓。

 

2KB 大小的 Cookie 壓縮前與壓縮後字節數相差 20% 左右,如果您的網站的 Cookie 在 2KB~3KB 左右,一天有 1 億的 PV,那麼一天就能夠產生 4TB 的帶寬流量了,從節省帶寬成本來說壓縮還是很有必要的。

 

三、    Session總結

如果Cookie太多,則會增加網絡流量,因此服務端Session機制應運而生;同一個客戶端每次和服務端交互時,不需要每次都傳回所有的Cookie值,而只是傳回一個ID即可;這個ID是客戶端第一次訪問服務器端自動生成的,而且保證每個客戶端是唯一的。ID通常是一個NAME爲JSESIONID的一個Cookie。

3.1 Session機制如何實現

  • 基於URL Path Parameter,默認支持;
  • 基於Cookie,如果沒有修改Context容器的Cookies標識,默認是支持的;
  • 基於SSL,默認不支持,只有connector.getAttributes(”SSLEnabled”)爲true時才支持;

 

客戶端禁止Cookie怎麼回傳SessionID面試回答:

  • 經常使用的一個技術是URL重寫,就是把SessionID直接附加到URL後面,比如:www.netesay.com?key1=value1&key2=value2
  • 表單隱藏;服務器會自動修改表單,添加一個隱藏字段,以便在提交表單的時候能夠把Session-ID傳遞迴服務器;比如:

<form name="testform" action="/xxx"> 
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764"> 
<input type="text"> 
</form> 

 

注意:

  • 第一中情況下,當瀏覽器不支持Cookie功能時,瀏覽器會自動將用戶的SessionCookieName重寫到URL參數中。關於這個SessionCookieName,如果在web.xml中配置session-config配置項,其name屬性就是這個SessionCookieName值;如果沒有配置name屬性那麼就是大家熟悉的JSESSIONID;接着Request根據這個SessionCookieName到Parameters中拿到SessionID並設置到request.setRequestSessionId中;
  • 如果客戶端支持Cookie,Tomcat仍然會解析Cookie中的SessionID,並且會覆蓋URL中的SessionID;
  • 第三種情況,將會根據javax.servlet.request.ssl_session屬性設置SessionID;

 

3.2 Session如何工作

有了SessionID服務端就可以創建HttpSession對象了,第一次觸發是requst的getSession()方法。如果當前的SessionID還沒有對應的HttpSession對象,那就創建一個新的,並將這個對象加到org.apache.catalina.Manager的sessions容器中保存。

Manager容器會管理所有的Session的生命週期,Session過期將會被回收,服務器關閉,Session將會被序列化保存到硬盤上。只要這個HttpSession對象存在,用戶就可以根據SessionID來獲取這個對象,也就達到了狀態的保持。

 

  • 面試回答:

當程序需要爲某個客戶端的請求創建一個session時,服務器首先檢查這個客戶端的請求裏是否已包含了一個session標識(稱爲session id),如果已包含則說明以前已經爲此客戶端創建過session,服務器就按照session id把這個session檢索出來使用(檢索不到,會新建一個),如果客戶端請求不包含session id,則爲此客戶端創建一個session並且生成一個與此session相關聯的session id,session id的值應該是一個既不會重複,又不容易被找到規律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。保存這個session id的方式可以採用cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發送給服務器。

 

  • Session如何恢復?

StandardManager 類負責 Servlet 容器中所有的 StandardSession 對象的生命週期管理。當 Servlet 容器重啓或關閉時StandardManager 負責持久化沒有過期的 StandardSession 對象,它會將所有的 StandardSession 對象持久化到一個以“SESSIONS.ser”爲文件名的文件中。到 Servlet 容器重啓時,也就是 StandardManager 初始化時,會重新讀取這個文件解析出所有 Session 對象,重新保存在 StandardManager 的 sessions 集合中。

 

 

四、    分佈式Session問題

在大型互聯網系統中,單獨使用 Cookie 和 Session 都是不可行的,原因很簡單。因爲如果使用 Cookie,可以很好地解決應用的分佈式部署問題,大型互聯網應用系統一個應用有上百臺機器,而且有很多不同的應用系統協同工作,由於 Cookie 是將值存儲在客戶端的瀏覽器裏,用戶每次訪問都會將最新的值帶回給處理該請求的服務器,所以也就解決了同一個用戶的請求可能不在同一臺服務器處理而導致的 Cookie 不一致的問題。

4.1 大面積使用Cookie的隱患

使用Cookie就是“誰家的孩子誰抱走”確實比較簡單,但是也帶來很多問題:

  • 一般客戶端瀏覽器對Cookie數量和大小有限制,50個以內,總大小4k以內;
  • Cookie管理混亂。在大型互聯網系統中,如果每個應用系統都自己管理每個應用使用的Cookie,將會導致混亂;
  • 安全令人擔憂,Cookie很容易被查看、篡改。

4.2 分佈式Session架構圖

4.2.1 統一訂閱服務器

統一通過訂閱服務器推送配置可以有效地集中管理資源,所以可以省去每個應用都來配置 Cookie,簡化 Cookie 的管理。

如果應用要使用一個新增 Cookie,可以通過一個統一的平臺來申請,申請通過纔將這個配置項增加到訂閱服務器。

如果是一個所有應用都要使用的全局 Cookie,那麼只需將這個 Cookie 通過訂閱服務器統一推送過去就行了,省去了要在每個應用中手動增加 Cookie 的配置。

關於這個訂閱服務器現在有很多開源的配置服務器,如 Zookeeper 集羣管理服務器,可以統一管理所有服務器的配置文件。

 

4.2.2 分佈式緩存

由於應用是一個集羣,所以不可能將創建的 Session 都保存在每臺應用服務器的內存中,因爲如果每臺服務器有幾十萬的訪問用戶,服務器的內存肯定不夠用,即使內存夠用,這些 Session 也無法同步到這個應用的所有服務器中。

所以要共享這些 Session 必須將它們存儲在一個分佈式緩存中,可以隨時寫入和讀取,而且性能要很好才能滿足要求。當前能滿足這個要求的系統有很多,如 MemCache 或者淘寶的開源分佈式緩存系統 Tair 都是很好的選擇。

 

 

4.2.3 存取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 中,這樣即使分佈式緩存出現問題也不會影響關鍵業務的正常運行。

 

 

 

4.2.4 跨域名共享Cookie和Session

參考:http://my.oschina.net/kevinair/blog/192829#OSC_h2_19

我們知道 Cookie 是有域名限制的,也就是一個域名下的 Cookie 不能被另一個域名訪問,所以如果在一個域名下已經登錄成功,如何訪問到另外一個域名的應用且保證登錄狀態仍然有效,這個問題大型網站應該經常會遇到。如何解決這個問題呢?

下面介紹一種處理方式,如圖 10-11 所示。

要實現 Session 同步,需要另外一個跳轉應用,這個應用可以被一個或者多個域名訪問,它的主要功能是從一個域名下取得 sessionID,然後將這個 sessionID 同步到另外一個域名下。這個 sessionID 其實就是一個 Cookie,相當於我們經常遇到的 JSESSIONID,所以要實現兩個域名下的 Session 同步,必須要將同一個 sessionID 作爲 Cookie 寫到兩個域名下。

總共 12 步,一個域名不用登錄就取到了另外一個域名下的 Session,當然這中間有些步驟還可以簡化,也可以做一些額外的工作,如可以寫一些需要的 Cookie,而不僅僅只傳一個 sessionID。

 

4.2.5 解決Cookie盜取問題

該框架還能處理 Cookie 被盜取的問題。如您的密碼沒有丟失,但是您的賬號卻有可能被別人登錄的情況,這種情況很可能就是因爲您登錄成功後,您的 Cookie 被別人盜取了,盜取您的 Cookie 的人將您的 Cookie 加入到他的瀏覽器,然後他就可以通過您的 Cookie 正常訪問您的個人信息了,這是一個非常嚴重的問題。

在這個框架中我們可以設置一個 Session 簽名,當用戶登錄成功後我們根據用戶的私密信息生成的一個簽名,以表示當前這個唯一的合法登錄狀態,然後將這個簽名作爲一個 Cookie 在當前這個用戶的瀏覽器進程中和服務器傳遞,用戶每次訪問服務器都會檢查這個簽名和從服務端分佈式緩存中取得的 Session 重新生成的簽名是否一致,如果不一致,顯然這個用戶的登錄狀態不合法,服務端將清除這個 sessionID 在分佈式緩存中的 Session 信息,讓用戶重新登錄。

 

五、       表單重複提交問題

網站中在很多地方都有表單重複提交問題,一種情況是用戶在網速慢的情況下可能會重複提交表單,還有就是惡意用戶通過程序來發送惡意請求,在這些情況下都要設計一個防止表單重複提交的機制。

參考:http://www.cnblogs.com/lovebaoqiang/p/3753488.html

(1)對按鈕進行控制

1

2

3

4

var $button = document.getElementById("btn");//獲取button對象

   $button.attr("disabled","disabled");

   window.setTimeout(function(){

   $button.removeAttr("disabled");},1000);

當然這麼操作的前提是,你在disbaled後,要對錶單進行處理,比如清空操作...

(2)做狀態位進行標識...

1

2

3

4

5

6

7

8

9

10

11

12

13

<script language="javascript">

var checkSubmitFlg = false;

function checkSubmit() {

    if (!checkSubmitFlg) {

        checkSubmitFlg = true;

        return true;

        }

    else{

            alert("不能重複提交");

            return false;

            }

}

</script>

<form action="XXXX" method="POST" onsubmit="return checkSubmit()">

內容:<input type="text" name="content" value=""/><br>

<input type="submit" value="提交"><br>

</form>

疑問:做這種標識,如何判斷你是換了另一個表單...

(3)加驗證碼,在每次提交時進行驗證...防止重複提交..

後臺程序解決:
大概主要用到一個token機制..
可以參考struts2的token機制,還有Token Session(令牌機制)
在每次表單提交時,action會調用isValidToken()方法,進行判斷是否相同的token,如果相同,進行提交,如果不同,認爲是重複提交..
當然這種前提是:在你打開新增表單的時候,會調用action,生成token碼..然後在你的表單和session中各保存一份,當提交調用action時,會進行token檢查,並且在action處理完後,會重置session 中的token,此時你再提交,就會導致token不一致,沒法提交...當然...意思就是要重新調用action生成新的token,和session保持一致...

要能夠防止表單重複提交,就要標識用戶的每一次訪問請求,使得每一次訪問對服務端來說都是唯一確定的。爲了標識用戶的每次訪問請求,可以在用戶請求一個表單域時增加一個隱藏表單項,這個表單項的值每次都是唯一的 token,如:

 <form id=”form” method=”post”> 

 <input type=hidden name=“crsf_token” value=“xxxx”/> 

</form>

當用戶在請求時生成這個唯一的 token 時,同時將這個 token 保存在用戶的 Session 中,等用戶提交請求時檢查這個 token 和當前的Session 中保存的 token 是否一致。如果一致,說明沒有重複提交,否則用戶提交上來的 token 已經不是當前的這個請求的合法 token。其工作過程如圖 10-12 所示。

5.1 發起請求

圖 10-12.工作過程

圖 10-12 是用戶發起對錶單頁面的請求過程,生成唯一的 token 需要一個算法,最簡單的就是可以根據一個種子作爲 key 生成一個隨機數,並保存在 Session 中,等下次用戶提交表單時做驗證。驗證表單的過程如圖 10-13 所示。

5.2 表單驗證

 

當用戶提交表單時會將請求時生成的 token 帶回來,這樣就可以和 Session 中保存的 token 做對比,從而確認這次表單驗證是否合法。

六、       總結

Cookie 和 Session 都是爲了保持用戶訪問的連續狀態,之所以要保持這種狀態,一方面是爲了方便業務實現,另一方面就是簡化服務端程序設計,提高訪問性能,但是這也帶來了另外一些挑戰,如安全問題、應用的分佈式部署帶來的 Session 的同步問題及跨域名 Session 的同步等一系列問題。本章分析了 Cookie 和 Session 的工作原理,並介紹了一致分佈式 Session 的解決方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章