深入理解Session和 Cookie

首先,Session與Cookie的作用都是爲了保持訪問用戶與後端服務器的交互狀態,也就是跟蹤用戶的整個會話。不同的是,Cookie通過在客戶端記錄信息確定用戶身份,Session通過在服務器端記錄信息確定用戶身份。

一、理解Cookie

1、Cookie爲什麼會出現?

Web應用程序是使用HTTP協議傳輸數據的,HTTP協議是無狀態的協議。一旦數據交換完畢,客戶端與服務器端的連接就會關閉,再次交換數據需要建立新的連接。這就意味着服務器無法從連接上跟蹤會話Cookie就是這樣的一種機制。它可以彌補HTTP協議無狀態的不足。在Session出現之前,基本上所有的網站都採用Cookie來跟蹤會話。
注意:Cookie功能需要瀏覽器的支持。如果瀏覽器不支持Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效。不同的瀏覽器採用不同的方式保存Cookie。

2、Cookie的屬性項

屬性項 屬性項介紹
NAME=VALUE 鍵值對,可以設置要保存的Key/Value,注意這裏的NAME不能和其他屬性項的名字一樣
Expires 過期時間,在設置的某個時間點後該Cookie就會失效,如expires=Wednesday,16-Nov-17 23:12:40 GMT
Max-Age 最大失效時間,表示在多少秒後失效
Domain 生成該Cookie的域名,即可以訪問該Cookie的域名
Path 生成該Cookie的路徑,即可以訪問該Cookie的路徑,設置爲“/”,則本域名下contextPath都可以訪問該Cookie,最後一個字符必須爲“/”
Secure 如果設置了這個屬性,那麼只會在使用安全協議連接時纔會回傳該Cookie,安全協議有HTTPS,SSL等。默認爲false。
Comment 註釋項,說明該Cookie有何用途
Version 該Cookie的版本號。0表示遵循Netscape的Cookie規範,1表示遵循W3C的RFC 2109規範
Port 該Cookie在什麼端口下可以回傳服務端,如果有多個端口,用逗號隔開
Discard 是否在會話結束後丟棄該Cookie項,默認爲false

下面詳細介紹Cookie的這些屬性項:

2.1 Cookie的有效期 

Cookie的maxAge決定着Cookie的有效期,單位爲秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。如果maxAge屬性爲正數,則表示該Cookie會在maxAge秒之後自動失效。瀏覽器會將maxAge爲正數的Cookie持久化,即寫到對應的Cookie文件中。無論客戶關閉了瀏覽器還是電腦,只要還在maxAge秒之前,登錄網站時該Cookie仍然有效。如果maxAge爲負數,則表示該Cookie僅在本瀏覽器窗口以及本窗口打開的子窗口內有效,關閉窗口後該Cookie即失效。maxAge爲負數的Cookie,爲臨時性Cookie,不會被持久化,不會被寫到Cookie文件中。Cookie信息保存在瀏覽器內存中,因此關閉瀏覽器該Cookie就消失了。Cookie默認的maxAge值爲–1。如果maxAge爲0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie的方法,因此通過設置該Cookie即時失效實現刪除Cookie的效果。失效的Cookie會被瀏覽器從Cookie文件或者內存中刪除。

response對象提供的Cookie操作方法只有一個添加操作add(Cookie cookie)。要想修改Cookie只能使用一個同名的Cookie來覆蓋原來的Cookie。
刪除時只需要把maxAge修改爲0即可。
注意:
(1)從客戶端讀取Cookie時,包括maxAge在內的其他屬性都是不可讀的,也不會被提交。瀏覽器提交Cookie時只會提交name與value屬性。
maxAge屬性只被瀏覽器用來判斷Cookie是否過期。
(2)修改、刪除Cookie時,新建的Cookie除value、maxAge之外的所有屬性,例如name、path、domain等,都要與原Cookie完全一樣。否則,
瀏覽器將視爲兩個不同的Cookie不予覆蓋,導致修改、刪除失敗。

2.2 Cookie的域名

Cookie是不可跨域名的。域名www.google.com頒發的Cookie不會被提交到域名www.baidu.com去。這是由Cookie的隱私安全機制決定的。隱私安全機制能夠禁止網站非法獲取其他網站的Cookie。

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie
cookie.setDomain(".helloweenvsfei.com");           // 設置域名
cookie.setPath("/");                              // 設置路徑
cookie.setMaxAge(Integer.MAX_VALUE);               // 設置有效期
response.addCookie(cookie);                       // 輸出到客戶端

可以修改本機C:\WINDOWS\system32\drivers\etc下的hosts文件來配置多個臨時域名,然後使用setCookie.jsp程序來設置跨域名Cookie驗證domain屬性。
注意:domain參數必須以點(".")開始。另外,name相同但domain不同的兩個Cookie是兩個不同的Cookie。如果想要兩個域名完全不同的網站共有Cookie,可以生成兩個Cookie,domain屬性分別爲兩個域名,輸出到客戶端。

正常情況下,同一個一級域名下的兩個二級域名如www.helloweenvsfei.com和images.helloweenvsfei.com也不能交互使用Cookie,因爲二者的域名並不嚴格相同。如果想所有helloweenvsfei.com名下的二級域名都可以使用該Cookie,需要設置Cookie的domain參數,例如:

2.3 Cookie的路徑

domain屬性決定運行訪問Cookie的域名,而path屬性決定允許訪問Cookie的路徑(ContextPath)。例如,如果只允許/session/下的程序使用Cookie,可以這麼寫:

Cookie cookie = new Cookie("time","20080808");     // 新建Cookie
cookie.setPath("/session/");                          // 設置路徑
response.addCookie(cookie);                           // 輸出到客戶端

設置爲“/”時允許所有路徑使用Cookie。path屬性需要使用符號“/”結尾。name相同但path不相同的兩個Cookie也是兩個不同的Cookie。注意:頁面只能獲取它屬於的Path的Cookie。例如/session/test/a.jsp不能獲取到路徑爲/session/abc/的Cookie。

2.4 Cookie的安全屬性

HTTP協議不僅是無狀態的,而且是不安全的。使用HTTP協議的數據不經過任何加密就直接在網絡上傳播,有被截獲的可能。使用HTTP協議傳輸很機密的內容是一種隱患。如果不希望Cookie在HTTP等非安全協議中傳輸,可以設置Cookie的secure屬性爲true。瀏覽器只會在HTTPS和SSL等安全協議中傳輸此類Cookie。下面的代碼設置secure屬性爲true:

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie
cookie.setSecure(true);                           // 設置安全屬性
response.addCookie(cookie);                        // 輸出到客戶端

提示:secure屬性並不能對Cookie內容加密,因而不能保證絕對的安全性。如果需要高安全性,需要在程序中對Cookie內容加密、解密,以防泄密。

3、Cookie的字符編碼

根據Cookie的規範,在Cookie中不能包含控制字符,僅能包含ASCII碼爲34~126的可見字符。因此如果字符不是ASCII碼還要進行轉換。

3.1 Unicode編碼:保存中文

中文與英文字符不同,中文屬於Unicode字符,在內存中佔4個字符,而英文屬於ASCII字符,內存中只佔2個字節。Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼。

注意:Cookie中保存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因爲瀏覽器不一定支持,而且JavaScript也不支持GBK編碼。

3.2 BASE64編碼:保存二進制文件

Cookie不僅可以使用ASCII字符與Unicode字符,還可以使用二進制數據。例如在Cookie中使用數字證書,提供安全度。使用二進制數據時也需要進行編碼。

4、Cookie的壓縮

Cookie在HTTP的頭部,所以通常的gzip和deflate針對HTTPBody的壓縮不能壓縮Cookie,如果Cookie的量非常大,則可以考慮將Cookie也做壓縮,壓縮方式是將Cookie的多個k/v對看成普通的文本,做文本壓縮。壓縮算法可以使用gzip和deflate算法。
如下是對Cookie進行壓縮和解壓縮的代碼:
//壓縮  
private void compressCookie(Cookie c,HttpServletResponse res){  
    try{  
        ByteArrayOutputStream bos = null;  
        bos = new ByteArrayOutputStream();  
        DeflaterOutputStream dos = new DeflaterOutputStream(bos);   
        dos.write(c.getValue().getBytes());  
        dos.close();  
        System.out.println("before compress length:" + c.getValue().getBytes().length);  
        String compress = new sum.misc.BASE64Encoder().encode(bos.toByteArray());  
        res.addCookie(new Cookie("compress",compress));  
        System.out.println("after compress length:" + compress.getBytes().length);  
       }catch(IOException e){  
            e.printStackTrace();  
       }  
  
}  
//解壓縮  
private void unCompressCookie(Cookie c){  
    try{  
       ByteArrayOutputStream out = new ByteArrayOutputStream();  
           byte[] compress = new sun.misc.BASE64Decoder().decodeBuffer(new String(c.getValue().getBytes()));  
       ByteArrayInputStream bis = new ByteArrayInputStream(compress);  
           InflaterInputStream inflater = new InflaterInputStream(bis);   
           byte[] b = new byte[1024];  
           int count;  
           while((count = inflater.read(b)) >= 0){  
               out.write(b,0,count);  
           }  
           inflater.close();  
           System.out.println(out.toByteArray());  
    }catch(Exception e){  
              e.printStackTrace();  
    }  
}  


5、Cookie是如何工作的?

可以用如下的方式來創建Cookie:
String getCookie(Cookie[] cookies,String key){
	if(cookies != null){
		for(Cookie cookie: cookies){
			if(cookie.getName().equals(key))
				return cookie.getValue();
		}
	}
	return null;
}

@Override
public void doGet(HttpServletRequest request,HttpServletResponse response)throws IOException,ServletException{
	Cookie[] cookies = request.getCookies();
	String username = getCookie(cookies,"username");
	String userAge = getCookie(cookies,"userAge");
	if(username == null)
		response.addCookie(new Cookie("username","aaa"));
		
	if(userAge == null)
		response.addCookie(new Cookie("userAge","17"));
	response.getHeaders("Set-Cookie");
}

Cookie是如何加到HTTP的Header中的呢?如下圖是Tomcat創建Set-Cookie響應頭的時序圖:

真正構建Cookie是在org.apache.catalina.connector.Response類中完成的,調用generateCookieString方法將Cookie對象構造成一個字符串,構造的字符串格式如username=“aaa”;Version="1";Max-Age=1000。然後將這個字符串命名爲Set-Cookie添加到MimeHeaders中。以上是服務端如何創建Cookie,那麼如何從客戶端獲取Cookie呢?當我們請求一個URL路徑時,瀏覽器會根據和這個URL路徑將符合條件的Cookie放在Request請求頭中傳回給服務器,服務端通過request.getCookies()來取得所有Cookie。

二、理解Session

1、Session爲什麼出現?

Cookie可以讓服務器程序跟蹤每個客戶端的訪問,但每次客戶端的訪問都必須傳回這些Cookie,如果Cookie很多,則增加了客戶端與服務端的數據傳輸量,而Session的出現正是爲了解決這個問題。
同一個客戶端每次和服務端交互時,不需要每次都傳回所有的Cookie值,而只要傳回一個ID,這個ID是客戶端第一次訪問服務器時生成的,而每個客戶端是唯一的,客戶端只有傳回這個ID就行了,這個ID通常是NAME爲ISESIONID的一個Cookie。

2、Session是如何工作的?

2.1 取得Session ID

(1)基於URL Path Parameter
(2)基於Cookie,如果沒有修改Context容器的Cookies標識,則默認也是支持的。
(3)基於SSL,默認不支持,只有connector.getAttribute("SSLEnabled")爲True時才支持。
在第一種情況下,當瀏覽器不支持Cookie時,瀏覽器會將用戶的SessionCookieName重寫到用戶請求的URL參數中,它的傳遞格式如/path/Servlet;name=value;name2=value2其中“Servlet”後面的K-V就是要傳遞的Path Parameters,服務器會從這個Path Parameters中拿到用戶配置的SessionCookieName。如果在web.xml中配置session-config配置項,其cookie-config下的name屬性就是這個SessionCookieName的值,如果沒有配置這一項,默認就是“JSESSIONID”。
如果客戶端支持Cookie,則tomcat仍然會解析Cookie中的Session ID,並覆蓋URL中的Session ID。
如果是第三種情況,則會根據javax.servlet.request.ssl_session屬性值設置Session ID。

2.2 創建HttpSession

當調用request.getSession()方法時,如果當前的Session ID還沒有對應的HttpSession對象,那麼就創建一個新的,並將這個對象加到org.apache.catalina.Manager的sessions容器保存。Manager類管理所以Session的生命週期,Session過期將被回收,服務器關閉,Session將被序列化到磁盤上。
如下是Session工作的時序圖:



從時序圖可看出,從Request中獲取的Session對象保存在org,apache.catalina.Manager類中,它的實現類是org,apache.catalina.session.StandardManager,通過requestedSessionId從StandardManager的sessions集合中取出StandardSession對象。StandardManager類負責Servlet容器中所有的StandardSession對象的生命週期管理。當Servlet容器重啓或關閉時,StandardManager負責持久化沒有過期的StandardSession對象,它會將所有的StandardSession對象持久化到一個以“SESSIONID.ser“爲文件名的文件中。當Servlet容器重啓時,也就是StandardManager初始化時,它會重新讀取這個文件,解析出所有Session對象,重新保存在StandardManager的sessions集合中。
注意:
(1)爲了獲得更高的存取速度,服務器一般把Session放在內存裏。每個用戶都會有一個獨立的Session。如果Session內容過於複雜,當大量客戶訪問服務器時可能會導致內存溢出。因此,Session裏的信息應該儘量精簡。
(2)只有訪問JSP、Servlet等程序時纔會創建Session,只訪問HTML、IMAGE等靜態資源並不會創建Session。
(3)爲防止內存溢出,服務器會把長時間內沒有活躍的Session從內存刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過服務器,Session就自動失效了。


參考:博客http://blog.csdn.net/fangaoxin/article/details/6952954/    和     《深入分析Java Web技術內幕》
            



發佈了108 篇原創文章 · 獲贊 20 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章