1. 程式人生 > >cookie 和 session機制

cookie 和 session機制

目錄

Cookie機制
什麼是Cookie
Cookie的不可跨域名性
Unicode編碼:儲存中文
BASE64編碼:儲存二進位制圖片
設定Cookie的所有屬性
Cookie的有效期
Cookie的修改、刪除
Cookie的域名
Cookie的路徑
Cookie的安全屬性
JavaScript操作Cookie
案例:永久登入
Session機制
什麼是Session
實現使用者登入
Session的生命週期
Session的有效期

Session的常用方法
Session對瀏覽器的要求
URL地址重寫
Session中禁止使用Cookie
Cookie與Session的區別

轉載: 理解Cookie和Session機制

會話(Session)跟蹤是Web程式中常用的技術,用來跟蹤使用者的整個會話。常用的會話跟蹤技術是Cookie與Session。Cookie通過在客戶端記錄資訊確定使用者身份,Session通過在伺服器端記錄資訊確定使用者身份。

本章將系統地講述Cookie與Session機制,並比較說明什麼時候不能用Cookie,什麼時候不能用Session。

Cookie機制

Cookie技術是客戶端的解決方案,Cookie就是由伺服器發給客戶端的特殊資訊,而這些資訊以文字檔案的方式存放在客戶端,然後客戶端每次向伺服器傳送請求的時候都會帶上這些特殊的資訊。讓我們說得更具體一些:當用戶使用瀏覽器訪問一個支援Cookie的網站的時候,使用者會提供包括使用者名稱在內的個人資訊並且提交至伺服器;接著,伺服器在向客戶端回傳相應的超文字的同時也會發回這些個人資訊,當然這些資訊並不是存放在HTTP響應體(Response Body)中的,而是存放於HTTP響應頭(Response Header);當客戶端瀏覽器接收到來自伺服器的響應之後,瀏覽器會將這些資訊存放在一個統一的位置,對於Windows作業系統而言,我們可以從: [系統盤]:\Documents and Settings[使用者名稱]\Cookies

目錄中找到儲存的Cookie;自此,客戶端再向伺服器傳送請求的時候,都會把相應的Cookie再次發回至伺服器。而這次,Cookie資訊則存放在HTTP請求頭(Request Header)了。有了Cookie這樣的技術實現,伺服器在接收到來自客戶端瀏覽器的請求之後,就能夠通過分析存放於請求頭的Cookie得到客戶端特有的資訊,從而動態生成與該客戶端相對應的內容。通常,我們可以從很多網站的登入介面中看到“請記住我”這樣的選項,如果你勾選了它之後再登入,那麼在下一次訪問該網站的時候就不需要進行重複而繁瑣的登入動作了,而這個功能就是通過Cookie實現的。

在程式中,會話跟蹤是很重要的事情。理論上,一個使用者的所有請求操作都應該屬於同一個會話,而另一個使用者的所有請求操作則應該屬於另一個會話,二者不能混淆。例如,使用者A在超市購買的任何商品都應該放在A的購物車內,不論是使用者A什麼時間購買的,這都是屬於同一個會話的,不能放入使用者B或使用者C的購物車內,這不屬於同一個會話。

而Web應用程式是使用HTTP協議傳輸資料的。HTTP協議是無狀態的協議。一旦資料交換完畢,客戶端與伺服器端的連線就會關閉,再次交換資料需要建立新的連線。這就意味著伺服器無法從連線上跟蹤會話。即使用者A購買了一件商品放入購物車內,當再次購買商品時伺服器已經無法判斷該購買行為是屬於使用者A的會話還是使用者B的會話了。要跟蹤該會話,必須引入一種機制。

Cookie就是這樣的一種機制。它可以彌補HTTP協議無狀態的不足。在Session出現之前,基本上所有的網站都採用Cookie來跟蹤會話。

如果你把Cookies看成為http協議的一個擴充套件的話,理解起來就容易的多了,其實本質上cookies就是http的一個擴充套件。有兩個http頭部是專門負責設定以及傳送cookie的,它們分別是Set-Cookie以及Cookie。當伺服器返回給客戶端一個http響應資訊時,其中如果包含Set-Cookie這個頭部時,意思就是指示客戶端建立一個cookie,並且在後續的http請求中自動傳送這個cookie到伺服器端,直到這個cookie過期。如果cookie的生存時間是整個會話期間的話,那麼瀏覽器會將cookie儲存在記憶體中,瀏覽器關閉時就會自動清除這個cookie。另外一種情況就是儲存在客戶端的硬碟中,瀏覽器關閉的話,該cookie也不會被清除,下次開啟瀏覽器訪問對應網站時,這個cookie就會自動再次傳送到伺服器端。一個cookie的設定以及傳送過程分為以下四步:

客戶端傳送一個http請求到伺服器端 伺服器端傳送一個http響應到客戶端,其中包含Set-Cookie頭部 客戶端傳送一個http請求到伺服器端,其中包含Cookie頭部 伺服器端傳送一個http響應到客戶端

這個通訊過程也可以用以下下示意圖來描述:

在客戶端的第二次請求中包含的Cookie頭部中,提供給了伺服器端可以用來唯一標識客戶端身份的資訊。這時,伺服器端也就可以判斷客戶端是否啟用了cookies。儘管,使用者可能在和應用程式互動的過程中突然禁用cookies的使用,但是,這個情況基本是不太可能發生的,所以可以不加以考慮,這在實踐中也被證明是對的。

這兩種傳遞資料的方式,比起用cookies來傳遞資料更穩定,因為cookie可能被禁用,但是以GET以及POST方式傳遞資料時,不存在這種情況。我們可以將PHPSESSID包含在http請求的url中,就像下面的例子一樣:

什麼是Cookie

Cookie意為“甜餅”,是由W3C組織提出,最早由Netscape社群發展的一種機制。目前Cookie已經成為標準,所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支援Cookie。

由於HTTP是一種無狀態的協議,伺服器單從網路連線上無從知道客戶身份。怎麼辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣伺服器就能從通行證上確認客戶身份了。這就是Cookie的工作原理。

Cookie實際上是一小段的文字資訊。客戶端請求伺服器,如果伺服器需要記錄該使用者狀態,就使用response向客戶端瀏覽器頒發一個Cookie。客戶端瀏覽器會把Cookie儲存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給伺服器。伺服器檢查該Cookie,以此來辨認使用者狀態。伺服器還可以根據需要修改Cookie的內容。

檢視某個網站頒發的Cookie很簡單。在瀏覽器位址列輸入javascript:alert (document. cookie)就可以了(需要有網才能檢視)。JavaScript指令碼會彈出一個對話方塊顯示本網站頒發的所有Cookie的內容,如圖所示。

上圖中彈出的對話方塊中顯示的為Baidu網站的Cookie。其中第一行BAIDUID記錄的就是筆者的身份helloweenvsfei,只是Baidu使用特殊的方法將Cookie資訊加密了。

注意:Cookie功能需要瀏覽器的支援。如果瀏覽器不支援Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效。不同的瀏覽器採用不同的方式儲存Cookie。IE瀏覽器會在“C:\Documents and Settings\你的使用者名稱\Cookies”資料夾下以文字檔案形式儲存,一個文字檔案儲存一個Cookie。

記錄使用者訪問次數

Java中把Cookie封裝成了javax.servlet.http.Cookie類。每個Cookie都是該Cookie類的物件。伺服器通過操作Cookie類物件對客戶端Cookie進行操作。通過request.getCookie()獲取客戶端提交的所有Cookie(以Cookie[]陣列形式返回),通過response.addCookie(Cookie cookie)向客戶端設定Cookie。

Cookie物件使用key-value屬性對的形式儲存使用者狀態,一個Cookie物件儲存一個屬性對,一個request或者response同時使用多個Cookie。因為Cookie類位於包javax.servlet.http.*下面,所以JSP中不需要import該類。

Cookie的不可跨域名性

很多網站都會使用Cookie。例如,Google會向客戶端頒發Cookie,Baidu也會向客戶端頒發Cookie。那瀏覽器訪問Google會不會也攜帶上Baidu頒發的Cookie呢?或者Google能不能修改Baidu頒發的Cookie呢?

答案是否定的。Cookie具有不可跨域名性。根據Cookie規範,瀏覽器訪問Google只會攜帶Google的Cookie,而不會攜帶Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。

Cookie在客戶端是由瀏覽器來管理的。瀏覽器能夠保證Google只會操作Google的Cookie而不會操作Baidu的Cookie,從而保證使用者的隱私安全。瀏覽器判斷一個網站是否能操作另一個網站Cookie的依據是域名。Google與Baidu的域名不一樣,因此Google不能操作Baidu的Cookie。

需要注意的是,雖然網站images.google.com與網站www.google.com同屬於Google,但是域名不一樣,二者同樣不能互相操作彼此的Cookie。

注意:使用者登入網站www.google.com之後會發現訪問images.google.com時登入資訊仍然有效,而普通的Cookie是做不到的。這是因為Google做了特殊處理。本章後面也會對Cookie做類似的處理。

Unicode編碼:儲存中文

中文與英文字元不同,中文屬於Unicode字元,在記憶體中佔4個字元,而英文屬於ASCII字元,記憶體中只佔2個位元組。Cookie中使用Unicode字元時需要對Unicode字元進行編碼,否則會亂碼。

提示:Cookie中儲存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因為瀏覽器不一定支援,而且JavaScript也不支援GBK編碼。

BASE64編碼:儲存二進位制圖片

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

注意:本程式僅用於展示Cookie中可以儲存二進位制內容,並不實用。由於瀏覽器每次請求伺服器都會攜帶Cookie,因此Cookie內容不宜過多,否則影響速度。Cookie的內容應該少而精。

設定Cookie的所有屬性

除了name與value之外,Cookie還具有其他幾個常用的屬性。每個屬性對應一個getter方法與一個setter方法。Cookie類的所有屬性如下所示。

String name:該Cookie的名稱。Cookie一旦建立,名稱便不可更改。 Object value:該Cookie的值。如果值為Unicode字元,需要為字元編碼。如果值為二進位制資料,則需要使用BASE64編碼。 int maxAge:該Cookie失效的時間,單位秒。如果為正數,則該Cookie在>maxAge秒之後失效。如果為負數,該Cookie為臨時Cookie,關閉瀏覽器即失效,瀏覽器也不會以任何形式儲存該Cookie。如果為0,表示刪除該Cookie。預設為–1。 boolean secure:該Cookie是否僅被使用安全協議傳輸。安全協議。安全協議有HTTPS,SSL等,在網路>上傳輸資料之前先將資料加密。預設為false。 String path:該Cookie的使用路徑。如果設定為“/sessionWeb/”,則只有contextPath為“/sessionWeb”的程式可以訪問該Cookie。如果設定為“/”,則本域名下contextPath都可以訪問該Cookie。注意最後一個字元必須為“/”。 >String domain:可以訪問該Cookie的域名。如果設定為“.google.com”,則所有以“google.com”結尾的域名都可以訪問該Cookie。注意第一個字元必須為“.”。 String comment:該Cookie的用處說明。瀏覽器顯示Cookie資訊的時候顯示該說明。 int version:該Cookie使>用的版本號。0表示遵循Netscape的Cookie規範,1表示遵循W3C的RFC 2109規範。

Cookie的有效期

Cookie的maxAge決定著Cookie的有效期,單位為秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。 如果maxAge屬性為正數,則表示該Cookie會在maxAge秒之後自動失效。瀏覽器會將maxAge為正數的Cookie持久化,即寫到對應的Cookie檔案中。無論客戶關閉了瀏覽器還是電腦,只要還在maxAge秒之前,登入網站時該Cookie仍然有效。下面程式碼中的Cookie資訊將永遠有效。

<span style="color:#333333"><code>Cookie cookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"username"</span>,
<span style="color:#a31515">"helloweenvsfei"</span>); 
<span style="color:green">// 新建Cookie</span>
cookie.setMaxAge(Integer.MAX_VALUE); 
<span style="color:green">// 設定生命週期為MAX_VALUE</span>
response.addCookie(cookie); 
<span style="color:green">// 輸出到客戶端</span></code></span>

如果maxAge為負數,則表示該Cookie僅在本瀏覽器視窗以及本視窗開啟的子視窗內有效,關閉視窗後該Cookie即失效。maxAge為負數的Cookie,為臨時性Cookie,不會被持久化,不會被寫到Cookie檔案中。Cookie資訊儲存在瀏覽器記憶體中,因此關閉瀏覽器該Cookie就消失了。Cookie預設的maxAge值為–1。

如果maxAge為0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie的方法,因此通過設定該Cookie即時失效實現刪除Cookie的效果。失效的Cookie會被瀏覽器從Cookie檔案或者記憶體中刪除:

<span style="color:#333333"><code>Cookie cookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"username"</span>;
<span style="color:#a31515">"helloweenvsfei"</span>); 
<span style="color:green">// 新建Cookie</span>
cookie.setMaxAge(0); 
<span style="color:green">// 設定生命週期為0,不能為負數</span>
response.addCookie(cookie); 
<span style="color:green">// 必須執行這一句</span></code></span>

response物件提供的Cookie操作方法只有一個新增操作add(Cookie cookie)。要想修改Cookie只能使用一個同名的Cookie來覆蓋原來的Cookie,達到修改的目的。刪除時只需要把maxAge修改為0即可。

注意:從客戶端讀取Cookie時,包括maxAge在內的其他屬性都是不可讀的,也不會被提交。瀏覽器提交Cookie時只會提交name與value屬性。maxAge屬性只被瀏覽器用來判斷Cookie是否過期。

Cookie的修改、刪除

Cookie並不提供修改、刪除操作。如果要修改某個Cookie,只需要新建一個同名的Cookie,新增到response中覆蓋原來的Cookie。如果要刪除某個Cookie,只需要新建一個同名的Cookie,並將maxAge設定為0,並新增到response中覆蓋原來的Cookie。注意是0而不是負數。負數代表其他的意義。讀者可以通過上例的程式進行驗證,設定不同的屬性。

注意:修改、刪除Cookie時,新建的Cookie除value、maxAge之外的所有屬性,例如name、path、domain等,都要與原Cookie完全一樣。否則,瀏覽器將視為兩個不同的Cookie不予覆蓋,導致修改、刪除失敗。

Cookie的域名

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

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

<span style="color:#333333"><code>Cookie cookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"time"</span>,<span style="color:#a31515">"20080808"</span>); <span style="color:green">// 新建Cookie</span>
cookie.setDomain(<span style="color:#a31515">".helloweenvsfei.com"</span>);
<span style="color:green">// 設定域名</span>
cookie.setPath(<span style="color:#a31515">"/"</span>); 
<span style="color:green">// 設定路徑</span>
cookie.setMaxAge(Integer.MAX_VALUE); 
<span style="color:green">// 設定有效期</span>
response.addCookie(cookie);
<span style="color:green">// 輸出到客戶端</span></code></span>

讀者可以修改本機C:\WINDOWS\system32\drivers\etc下的hosts檔案來配置多個臨時域名,然後使用setCookie.jsp程式來設定跨域名Cookie驗證domain屬性。

注意:domain引數必須以點(".")開始。另外,name相同但domain不同的兩個Cookie是兩個不同的Cookie。如果想要兩個域名完全不同的網站共有Cookie,可以生成兩個Cookie,domain屬性分別為兩個域名,輸出到客戶端。

Cookie的路徑

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

<span style="color:#333333"><code>Cookie cookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"time"</span>,
<span style="color:#a31515">"20080808"</span>);
 <span style="color:green">// 新建Cookie</span>
cookie.setPath(<span style="color:#a31515">"/session/"</span>);
 <span style="color:green">// 設定路徑</span>
response.addCookie(cookie); <span style="color:green">// 輸出到客戶端</span></code></span>

設定為“/”時允許所有路徑使用Cookie。path屬性需要使用符號“/”結尾。name相同但domain不同的兩個Cookie也是兩個不同的Cookie。

注意:頁面只能獲取它屬於的Path的Cookie。例如/session/test/a.jsp不能獲取到路徑為/session/abc/的Cookie。使用時一定要注意。

  1. domain表示的是cookie所在的域,預設為請求的地址,如網址為www.test.com/test/test.aspx,那麼domain預設為www.test.com。而跨域訪問,如域A為t1.test.com,域B為t2.test.com,那麼在域A生產一個令域A和域B都能訪問的cookie就要將該cookie的domain設定為.test.com;如果要在域A生產一個令域A不能訪問而域B能訪問的cookie就要將該cookie的domain設定為t2.test.com。

  2. path表示cookie所在的目錄,預設為/,就是根目錄。在同一個伺服器上有目錄如下:/test/,/test/cd/,/test/dd/,現設一個cookie1的path為/test/,cookie2的path為/test/cd/,那麼test下的所有頁面都可以訪問到cookie1,而/test/和/test/dd/的子頁面不能訪問cookie2。這是因為cookie能讓其path路徑下的頁面訪問。

  3. 瀏覽器會將domain和path都相同的cookie儲存在一個檔案裡,cookie間用*隔開。

Cookie的安全屬性

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

<span style="color:#333333"><code>Cookie cookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"time"</span>, 
<span style="color:#a31515">"20080808"</span>); <span style="color:green">// 新建Cookie</span>
cookie.setSecure(<span style="color:#0000ff">true</span>); 
<span style="color:green">// 設定安全屬性</span>
response.addCookie(cookie);
<span style="color:green">// 輸出到客戶端</span></code></span>

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

JavaScript操作Cookie

Cookie是儲存在瀏覽器端的,因此瀏覽器具有操作Cookie的先決條件。瀏覽器可以使用指令碼程式如JavaScript或者VBScript等操作Cookie。這裡以JavaScript為例介紹常用的Cookie操作。例如下面的程式碼會輸出本頁面所有的Cookie。

<script>document.write(document.cookie);</script>

由於JavaScript能夠任意地讀寫Cookie,有些好事者便想使用JavaScript程式去窺探使用者在其他網站的Cookie。不過這是徒勞的,W3C組織早就意識到JavaScript對Cookie的讀寫所帶來的安全隱患並加以防備了,W3C標準的瀏覽器會阻止JavaScript讀寫任何不屬於自己網站的Cookie。換句話說,A網站的JavaScript程式讀寫B網站的Cookie不會有任何結果。

案例:永久登入

如果使用者是在自己家的電腦上上網,登入時就可以記住他的登入資訊,下次訪問時不需要再次登入,直接訪問即可。實現方法是把登入資訊如賬號、密碼等儲存在Cookie中,並控制Cookie的有效期,下次訪問時再驗證Cookie中的登入資訊即可。

儲存登入資訊有多種方案。最直接的是把使用者名稱與密碼都保持到Cookie中,下次訪問時檢查Cookie中的使用者名稱與密碼,與資料庫比較。這是一種比較危險的選擇,一般不把密碼等重要資訊儲存到Cookie中。

還有一種方案是把密碼加密後儲存到Cookie中,下次訪問時解密並與資料庫比較。這種方案略微安全一些。如果不希望儲存密碼,還可以把登入的時間戳儲存到Cookie與資料庫中,到時只驗證使用者名稱與登入時間戳就可以了。

這幾種方案驗證賬號時都要查詢資料庫。

本例將採用另一種方案,只在登入時查詢一次資料庫,以後訪問驗證登入資訊時不再查詢資料庫。實現方式是把賬號按照一定的規則加密後,連同賬號一塊儲存到Cookie中。下次訪問時只需要判斷賬號的加密規則是否正確即可。本例把賬號儲存到名為account的Cookie中,把賬號連同金鑰用MD1演算法加密後儲存到名為ssid的Cookie中。驗證時驗證Cookie中的賬號與金鑰加密後是否與Cookie中的ssid相等。相關程式碼如下: loginCookie.jsp:

<span style="color:#333333"><code><%@ page language=<span style="color:#a31515">"java"</span> pageEncoding=<span style="color:#a31515">"UTF-8"</span> isErrorPage=<span style="color:#a31515">"false"</span> %>
<%!                                                  <span style="color:green">// JSP方法</span>
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> String KEY =<span style="color:#a31515">":[email protected]"</span>; <span style="color:green">// 金鑰 </span>

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">final</span> <span style="color:#0000ff">static</span> String <span style="color:#a31515">calcMD1</span>(String ss) { <span style="color:green">// MD1 加密演算法</span>
       String s = ss == <span style="color:#0000ff">null</span> ? <span style="color:#a31515">""</span> : ss; <span style="color:green">// 若為null返回空</span>
       <span style="color:#0000ff">char</span> hexDigits[] = { <span style="color:#a31515">'0'</span>,<span style="color:#a31515">'1'</span>, <span style="color:#a31515">'2'</span>, <span style="color:#a31515">'3'</span>, <span style="color:#a31515">'4'</span>, <span style="color:#a31515">'1'</span>, <span style="color:#a31515">'6'</span>, <span style="color:#a31515">'7'</span>, <span style="color:#a31515">'8'</span>, <span style="color:#a31515">'9'</span>, <span style="color:#a31515">'a'</span>, <span style="color:#a31515">'b'</span>, <span style="color:#a31515">'c'</span>, <span style="color:#a31515">'d'</span>, <span style="color:#a31515">'e'</span>, <span style="color:#a31515">'f'</span> }; <span style="color:green">// 字典</span>
       <span style="color:#0000ff">try</span> {
           <span style="color:#0000ff">byte</span>[] strTemp = s.getBytes();                          <span style="color:green">// 獲取位元組</span>
           MessageDigestmdTemp = MessageDigest.getInstance(<span style="color:#a31515">"MD1"</span>); <span style="color:green">// 獲取MD1</span>
           mdTemp.update(strTemp);                                <span style="color:green">// 更新資料</span>
           <span style="color:#0000ff">byte</span>[] md =mdTemp.digest();                        <span style="color:green">// 加密</span>
           <span style="color:#0000ff">int</span> j =md.length;                                 <span style="color:green">// 加密後的長度</span>
           <span style="color:#0000ff">char</span> str[] = <span style="color:#0000ff">new</span> <span style="color:#0000ff">char</span>[j * 2];                       <span style="color:green">// 新字串陣列</span>
           <span style="color:#0000ff">int</span> k =0;                                         <span style="color:green">// 計數器k</span>
           <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i< j; i++) {                       <span style="color:green">// 迴圈輸出</span>
               <span style="color:#0000ff">byte</span> byte0 = md[i];
               str[k++] = hexDigits[byte0 >>> 4 & 0xf];
               str[k++] = hexDigits[byte0 & 0xf];
           }
           <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> String(str);                             <span style="color:green">// 加密後字串</span>
       } <span style="color:#0000ff">catch</span> (Exception e){<span style="color:#0000ff">return</span> <span style="color:#0000ff">null</span>; }
    }
%>
<%
   request.setCharacterEncoding(<span style="color:#a31515">"UTF-8"</span>);          <span style="color:green">// 設定request編碼</span>
   response.setCharacterEncoding(<span style="color:#a31515">"UTF-8"</span>);        <span style="color:green">// 設定response編碼</span>

   String action =request.getParameter(<span style="color:#a31515">"action"</span>); <span style="color:green">// 獲取action引數</span>

   <span style="color:#0000ff">if</span>(<span style="color:#a31515">"login"</span>.equals(action)) {                       <span style="color:green">// 如果為login動作</span>
        String account =request.getParameter(<span style="color:#a31515">"account"</span>); <span style="color:green">// 獲取account引數</span>
        String password =request.getParameter(<span style="color:#a31515">"password"</span>); <span style="color:green">// 獲取password引數</span>
        <span style="color:#0000ff">int</span> timeout = <span style="color:#0000ff">new</span> Integer(request.getParameter(<span style="color:#a31515">"timeout"</span>)); <span style="color:green">// 獲取timeout引數</span>

        String ssid =calcMD1(account + KEY); <span style="color:green">// 把賬號、金鑰使用MD1加密後儲存</span>

        Cookie accountCookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"account"</span>, account); <span style="color:green">// 新建Cookie</span>
        accountCookie.setMaxAge(timeout);              <span style="color:green">// 設定有效期</span>

        Cookie ssidCookie =<span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"ssid"</span>, ssid);   <span style="color:green">// 新建Cookie</span>
       ssidCookie.setMaxAge(timeout);                 <span style="color:green">// 設定有效期</span>

       response.addCookie(accountCookie);             <span style="color:green">// 輸出到客戶端</span>
       response.addCookie(ssidCookie);            <span style="color:green">// 輸出到客戶端</span>

       <span style="color:green">// 重新請求本頁面,引數中帶有時間戳,禁止瀏覽器快取頁面內容</span>
       response.sendRedirect(request.getRequestURI() + <span style="color:#a31515">"?"</span> + System.currentTimeMillis());
       <span style="color:#0000ff">return</span>;
    } <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span>(<span style="color:#a31515">"logout"</span>.equals(action)) {                  <span style="color:green">// 如果為logout動作</span>
       CookieaccountCookie = <span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"account"</span>, <span style="color:#a31515">""</span>); <span style="color:green">// 新建Cookie,內容為空</span>
       accountCookie.setMaxAge(0); <span style="color:green">// 設定有效期為0,刪除</span>

       Cookie ssidCookie =<span style="color:#0000ff">new</span> Cookie(<span style="color:#a31515">"ssid"</span>, <span style="color:#a31515">""</span>); <span style="color:green">// 新建Cookie,內容為空</span>
       ssidCookie.setMaxAge(0);                   <span style="color:green">// 設定有效期為0,刪除</span>
       response.addCookie(accountCookie);         <span style="color:green">// 輸出到客戶端</span>
       response.addCookie(ssidCookie);         <span style="color:green">// 輸出到客戶端</span>
       <span style="color:green">// 重新請求本頁面,引數中帶有時間戳,禁止瀏覽器快取頁面內容</span>
       response.sendRedirect(request.getRequestURI() + <span style="color:#a31515">"?"</span> + System.currentTimeMillis());
       <span style="color:#0000ff">return</span>;
    }
    <span style="color:#0000ff">boolean</span> login = <span style="color:#0000ff">false</span>;                        <span style="color:green">// 是否登入</span>
    String account = <span style="color:#0000ff">null</span>;                        <span style="color:green">// 賬號</span>
    String ssid = <span style="color:#0000ff">null</span>;                           <span style="color:green">// SSID標識</span>

    <span style="color:#0000ff">if</span>(request.getCookies() !=<span style="color:#0000ff">null</span>) {               <span style="color:green">// 如果Cookie不為空</span>
        <span style="color:#0000ff">for</span>(Cookie cookie : request.getCookies()) {  <span style="color:green">// 遍歷Cookie</span>
           <span style="color:#0000ff">if</span>(cookie.getName().equals(<span style="color:#a31515">"account"</span>))  <span style="color:green">// 如果Cookie名為 account</span>
               account = cookie.getValue();       <span style="color:green">// 儲存account內容</span>
           <span style="color:#0000ff">if</span>(cookie.getName().equals(<span style="color:#a31515">"ssid"</span>)) <span style="color:green">// 如果為SSID</span>
               ssid = cookie.getValue();          <span style="color:green">// 儲存SSID內容</span>
        }
    }
    <span style="color:#0000ff">if</span>(account != <span style="color:#0000ff">null</span> && ssid !=<span style="color:#0000ff">null</span>) {    <span style="color:green">// 如果account、SSID都不為空</span>
        login = ssid.equals(calcMD1(account + KEY)); <span style="color:green">// 如果加密規則正確, 則視為已經登入</span>
    }
%>
<!DOCTYPE HTML PUBLIC <span style="color:#a31515">"-//W3C//DTD HTML 4.01Transitional//EN"</span>>
       <legend><%= login ? <span style="color:#a31515">"歡迎您回來"</span> : <span style="color:#a31515">"請先登入"</span>%></legend>
        <% <span style="color:#0000ff">if</span>(login){%>
            歡迎您, ${cookie.account.value }. &nbsp;&nbsp;
           <a href=<span style="color:#a31515">"${pageContext.request.requestURI }?action=logout"</span>>
            登出</a>
        <% } <span style="color:#0000ff">else</span> { %>
        <form action=<span style="color:#a31515">"${ pageContext.request.requestURI }?action=login"</span> method=<span style="color:#a31515">"post"</span>>
           <table>
               <tr><td>賬號: </td>
                   <td><input type=<span style="color:#a31515">"text"</span>name=<span style="color:#a31515">"account"</span> style=<span style="color:#a31515">"width:
                   200px; "</span>></td>
               </tr>
               <tr><td>密碼: </td>
                   <td><inputtype=<span style="color:#a31515">"password"</span> name=<span style="color:#a31515">"password"</span>></td>
               </tr>
               <tr>
                   <td>有效期: </td>
                   <td><inputtype=<span style="color:#a31515">"radio"</span> name=<span style="color:#a31515">"timeout"</span> value=<span style="color:#a31515">"-1"</span>
                   checked> 關閉瀏覽器即失效 <br/> <input type=<span style="color:#a31515">"radio"</span> 
                   name=<span style="color:#a31515">"timeout"</span> value=<span style="color:#a31515">"<%= 30 *24 * 60 * 60 %>"</span>> 30天
                   內有效 <br/><input type=<span style="color:#a31515">"radio"</span> name=<span style="color:#a31515">"timeout"</span> value= 
                   <span style="color:#a31515">"<%= Integer.MAX_VALUE %>"</span>> 永久有效 <br/> </td> </tr>
               <tr><td></td>
                   <td><input type=<span style="color:#a31515">"submit"</span>value=<span style="color:#a31515">" 登  錄 "</span> <span style="color:#0000ff">class</span>= 
                   <span style="color:#a31515">"button"</span>></td>
               </tr>
           </table>
        </form>
        <% } %></code></span>

登入時可以選擇登入資訊的有效期:關閉瀏覽器即失效、30天內有效與永久有效。通過設定Cookie的age屬性來實現,注意觀察程式碼。執行效果如圖1.7所示。

提示:該加密機制中最重要的部分為演算法與金鑰。由於MD1演算法的不可逆性,即使使用者知道了賬號與加密後的字串,也不可能解密得到金鑰。因此,只要保管好金鑰與演算法,該機制就是安全的。


Session機制

除了使用Cookie,Web應用程式中還經常使用Session來記錄客戶端狀態。Session是伺服器端使用的一種記錄客戶端狀態的機制,使用上比Cookie簡單一些,相應的也增加了伺服器的儲存壓力。

Session技術則是服務端的解決方案,它是通過伺服器來保持狀態的。由於Session這個詞彙包含的語義很多,因此需要在這裡明確一下 Session的含義。首先,我們通常都會把Session翻譯成會話,因此我們可以把客戶端瀏覽器與伺服器之間一系列互動的動作稱為一個 Session。從這個語義出發,我們會提到Session持續的時間,會提到在Session過程中進行了什麼操作等等;其次,Session指的是伺服器端為客戶端所開闢的儲存空間,在其中儲存的資訊就是用於保持狀態。從這個語義出發,我們則會提到往Session中存放什麼內容,如何根據鍵值從 Session中獲取匹配的內容等。要使用Session,第一步當然是建立Session了。那麼Session在何時建立呢?當然還是在伺服器端程式執行的過程中建立的,不同語言實現的應用程式有不同建立Session的方法,而在Java中是通過呼叫HttpServletRequest的getSession方法(使用true作為引數)建立的。在建立了Session的同時,伺服器會為該Session生成唯一的Session id,而這個Session id在隨後的請求中會被用來重新獲得已經建立的Session;在Session被建立之後,就可以呼叫Session相關的方法往Session中增加內容了,而這些內容只會儲存在伺服器中,發到客戶端的只有Session id;當客戶端再次傳送請求的時候,會將這個Session id帶上,伺服器接受到請求之後就會依據Session id找到相應的Session,從而再次使用之。正式這樣一個過程,使用者的狀態也就得以保持了。

什麼是Session

Session是另一種記錄客戶狀態的機制,不同的是Cookie儲存在客戶端瀏覽器中,而Session儲存在伺服器上。客戶端瀏覽器訪問伺服器的時候,伺服器把客戶端資訊以某種形式記錄在伺服器上。這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查詢該客戶的狀態就可以了。

如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那麼Session機制就是通過檢查伺服器上的“客戶明細表”來確認客戶身份。Session相當於程式在伺服器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。

實現使用者登入

Session對應的類為javax.servlet.http.HttpSession類。每個來訪者對應一個Session物件,所有該客戶的狀態資訊都儲存在這個Session物件裡。Session物件是在客戶端第一次請求伺服器的時候建立的。Session也是一種key-value的屬性對,通過getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法讀寫客戶狀態資訊。Servlet裡通過request.getSession()方法獲取該客戶的Session,例如:

<span style="color:#333333"><code>HttpSession session = request.getSession();       <span style="color:green">// 獲取Session物件</span>
session.setAttribute(<span style="color:#a31515">"loginTime"</span>, <span style="color:#0000ff">new</span> Date());     <span style="color:green">// 設定Session中的屬性</span>

out.println(<span style="color:#a31515">"登入時間為:"</span> +(Date)session.getAttribute(<span style="color:#a31515">"loginTime"</span>));      <span style="color:green">// 獲取Session屬性</span></code></span>

request還可以使用getSession(boolean create)來獲取Session。區別是如果該客戶的Session不存在,request.getSession()方法會返回null,而getSession(true)會先建立Session再將Session返回。

Servlet中必須使用request來程式設計式獲取HttpSession物件,而JSP中內建了Session隱藏物件,可以直接使用。如果使用聲明瞭<%@page session="false" %>,則Session隱藏物件不可用。下面的例子使用Session記錄客戶賬號資訊。 session.jsp:

<span style="color:#333333"><code><%@ page language=<span style="color:#a31515">"java"</span> pageEncoding=<span style="color:#a31515">"UTF-8"</span>%>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"com.helloweenvsfei.sessionWeb.bean.Person"</span>/>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"java.text.SimpleDateFormat"</span>/>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"java.text.DateFormat"</span>/>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"java.util.Date"</span>/>
<%!
    DateFormat dateFormat = newSimpleDateFormat(<span style="color:#a31515">"yyyy-MM-dd"</span>);         <span style="color:green">// 日期格式化器</span>
%>
<%
    response.setCharacterEncoding(<span style="color:#a31515">"UTF-8"</span>);        <span style="color:green">// 設定request編碼</span>
    Person[] persons =
    {           
       <span style="color:green">// 基礎資料,儲存三個人的資訊</span>
        <span style="color:#0000ff">new</span> Person(<span style="color:#a31515">"Liu Jinghua"</span>,<span style="color:#a31515">"password1"</span>, 34, dateFormat.parse
        (<span style="color:#a31515">"1982-01-01"</span>)),
        <span style="color:#0000ff">new</span> Person(<span style="color:#a31515">"Hello Kitty"</span>,<span style="color:#a31515">"hellokitty"</span>, 23, dateFormat.parse
        (<span style="color:#a31515">"1984-02-21"</span>)),
        <span style="color:#0000ff">new</span> Person(<span style="color:#a31515">"Garfield"</span>, <span style="color:#a31515">"garfield_pass"</span>,23, dateFormat.parse
        (<span style="color:#a31515">"1994-09-12"</span>))
     };

    String message = <span style="color:#a31515">""</span>;                      <span style="color:green">// 要顯示的訊息</span>

    <span style="color:#0000ff">if</span>(request.getMethod().equals(<span style="color:#a31515">"POST"</span>))
    { 
        <span style="color:green">// 如果是POST登入       </span>
        <span style="color:#0000ff">for</span>(Person person :persons)
        {           
           <span style="color:green">// 遍歷基礎資料,驗證賬號、密碼</span>
           <span style="color:green">// 如果使用者名稱正確且密碼正確</span>
           <span style="color:#0000ff">if</span>(person.getName().equalsIgnoreCase(request.getParameter(<span style="color:#a31515">"username"</span>))&&person.getPassword().equals(request.getParameter(<span style="color:#a31515">"password"</span>)))
           {              
               <span style="color:green">// 登入成功,設定將使用者的資訊以及登入時間儲存到Session</span>
               session.setAttribute(<span style="color:#a31515">"person"</span>, person);                   <span style="color:green">// 儲存登入的Person</span>
               session.setAttribute(<span style="color:#a31515">"loginTime"</span>, <span style="color:#0000ff">new</span> Date());          <span style="color:green">// 儲存登入的時間              </span>
               response.sendRedirect(request.getContextPath() + <span style="color:#a31515">"/welcome.jsp"</span>);
               <span style="color:#0000ff">return</span>;
            }
        }      
        message = <span style="color:#a31515">"使用者名稱密碼不匹配,登入失敗。"</span>;       <span style="color:green">// 登入失敗</span>
    }
%>
<!DOCTYPE HTML PUBLIC <span style="color:#a31515">"-//W3C//DTD HTML 4.01Transitional//EN"</span>>
<html>
    <span style="color:green">// ... HTML程式碼為一個FORM表單,程式碼略,請看隨書光碟</span>
</html></code></span>

登入介面驗證使用者登入資訊,如果登入正確,就把使用者資訊以及登入時間儲存進Session,然後轉到歡迎頁面welcome.jsp。welcome.jsp中從Session中獲取資訊,並將使用者資料顯示出來。 welcome.jsp:

<span style="color:#333333"><code><%@ page language=<span style="color:#a31515">"java"</span> pageEncoding=<span style="color:#a31515">"UTF-8"</span>%>
<jsp:directive.pageimport=<span style="color:#a31515">"com.helloweenvsfei.sessionWeb.bean.Person"</span>/>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"java.text.SimpleDateFormat"</span>/>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"java.text.DateFormat"</span>/>
<jsp:directive.page <span style="color:#0000ff">import</span>=<span style="color:#a31515">"java.util.Date"</span>/>
<%!
    DateFormat dateFormat = newSimpleDateFormat(<span style="color:#a31515">"yyyy-MM-dd"</span>);         <span style="color:green">// 日期格式化器</span>
%>
<%
    Person person =(Person)session.getAttribute(<span style="color:#a31515">"person"</span>);                       <span style="color:green">// 獲取登入的person</span>
    Date loginTime =(Date)session.getAttribute(<span style="color:#a31515">"loginTime"</span>);                     <span style="color:green">// 獲取登入時間</span>
%>
    <span style="color:green">// ... 部分HTML程式碼略</span>
            <table>
               <tr><td>您的姓名:</td>
                   <td><%= person.getName()%></td>
               </tr>
               <tr><td>登入時間:</td>
                   <td><%= loginTime%></td>
               </tr>
               <tr><td>您的年齡:</td>
                   <td><%= person.getAge()%></td>
               </tr>
               <tr><td>您的生日:</td>
                   <td><%=dateFormat.format(person.getBirthday()) %></td>
               </tr>
            </table></code></span>

程式執行效果如圖所示。

注意:程式中Session中直接儲存了Person類物件與Date類物件,使用起來要比Cookie方便。當多個客戶端執行程式時,伺服器會儲存多個客戶端的Session。獲取Session的時候也不需要宣告獲取誰的Session。Session機制決定了當前客戶只會獲取到自己的Session,而不會獲取到別人的Session。各客戶的Session也彼此獨立,互不可見。

提示:Session的使用比Cookie方便,但是過多的Session儲存在伺服器記憶體中,會對伺服器造成壓力。

Session的生命週期

Session儲存在伺服器端。為了獲得更高的存取速度,伺服器一般把Session放在記憶體裡。每個使用者都會有一個獨立的Session。如果Session內容過於複雜,當大量客戶訪問伺服器時可能會導致記憶體溢位。因此,Session裡的資訊應該儘量精簡。

Session在使用者第一次訪問伺服器的時候自動建立。需要注意只有訪問JSP、Servlet等程式時才會建立Session,只訪問HTML、IMAGE等靜態資源並不會建立Session。如果尚未生成Session,也可以使用request.getSession(true)強制生成Session。

Session生成後,只要使用者繼續訪問,伺服器就會更新Session的最後訪問時間,並維護該Session。使用者每訪問伺服器一次,無論是否讀寫Session,伺服器都認為該使用者的Session“活躍(active)”了一次。

Session的有效期

由於會有越來越多的使用者訪問伺服器,因此Session也會越來越多。為防止記憶體溢位,伺服器會把長時間內沒有活躍的Session從記憶體刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過伺服器,Session就自動失效了。

Session的超時時間為maxInactiveInterval屬性,可以通過對應的getMaxInactiveInterval()獲取,通過setMaxInactiveInterval(longinterval)修改。

Session的超時時間也可以在web.xml中修改。另外,通過呼叫Session的invalidate()方法可以使Session失效。

Session的常用方法

Session中包括各種方法,使用起來要比Cookie方便得多。Session的常用方法如下所示。

void setAttribute(String attribute, Object value):設定Session屬性。value引數可以為任何Java Object。通常為Java Bean。value資訊不宜過大 String getAttribute(String attribute):返回Session屬性 Enumeration getAttributeNames():返回Session中存在的屬性名 >void removeAttribute(String attribute):移除Session屬性 String getId():返回Session的ID。該ID由伺服器自動建立,不會重複 long getCreationTime():返回Session的建立日期。返回型別為long,常被轉化為Date型別,例如:Date createTime = new Date(session.get >CreationTime()) long getLastAccessedTime():返回Session的最後活躍時間。返回型別為long int getMaxInactiveInterval():返回Session的超時時間。單位為秒。超過該時間沒有訪問,伺服器認為該Session失效 void setMaxInactiveInterval(int second):設定Session的>超時時間。單位為秒 void putValue(String attribute, Object value):不推薦的方法。已經被setAttribute(String attribute, Object Value)替代 Object getValue(String attribute):不被推薦的方法。已經被getAttribute(String attr)替代 boolean isNew():返回該Session是否是>新建立的 void invalidate():使該Session失效

Tomcat中Session的預設超時時間為20分鐘。通過setMaxInactiveInterval(int seconds)修改超時時間。可以修改web.xml改變Session的預設超時時間。例如修改為60分鐘:

<span style="color:#333333"><code><span style="color:#0000ff"><<span style="color:#0000ff">session-config</span>></span>
   <span style="color:#0000ff"><<span style="color:#0000ff">session-timeout</span>></span>60<span style="color:#0000ff"></<span style="color:#0000ff">session-timeout</span>></span>      <span style="color:green"><!-- 單位:分鐘 --></span>
<span style="color:#0000ff"></<span style="color:#0000ff">session-config</span>></span></code></span>

注意:引數的單位為分鐘,而setMaxInactiveInterval(int s)單位為秒。

在server.xml中定義context時採用如下定義(單位為秒):

<span style="color:#333333"><code><span style="color:#0000ff"><<span style="color:#0000ff">Context</span> <span style="color:red">path</span>=<span style="color:#a31515">"/livsorder"</span> <span style="color:red">docBase</span>=<span style="color:#a31515">"/home/httpd/html/livsorder"</span> <span style="color:red">defaultSessionTimeOut</span>=<span style="color:#a31515">"3600"</span> <span style="color:red">isWARExpanded</span>=<span style="color:#a31515">"true"</span>
    <span style="color:red">isWARValidated</span>=<span style="color:#a31515">"false"</span> <span style="color:red">isInvokerEnabled</span>=<span style="color:#a31515">"true"</span>
    <span style="color:red">isWorkDirPersistent</span>=<span style="color:#a31515">"false"</span>/></span></code></span>

Session對瀏覽器的要求

雖然Session儲存在伺服器,對客戶端是透明的,它的正常執行仍然需要客戶端瀏覽器的支援。這是因為Session需要使用Cookie作為識別標誌。HTTP協議是無狀態的,Session不能依據HTTP連線來判斷是否為同一客戶,因此伺服器向客戶端瀏覽器傳送一個名為JSESSIONID的Cookie,它的值為該Session的id(也就是HttpSession.getId()的返回值)。Session依據該Cookie來識別是否為同一使用者。

該Cookie為伺服器自動生成的,它的maxAge屬性一般為–1,表示僅當前瀏覽器內有效,並且各瀏覽器視窗間不共享,關閉瀏覽器就會失效。

因此同一機器的兩個瀏覽器視窗訪問伺服器時,會生成兩個不同的Session。但是由瀏覽器視窗內的連結、指令碼等開啟的新視窗(也就是說不是雙擊桌面瀏覽器圖示等開啟的視窗)除外。這類子視窗會共享父視窗的Cookie,因此會共享一個Session。

注意:新開的瀏覽器視窗會生成新的Session,但子視窗除外。子視窗會共用父視窗的Session。例如,在連結上右擊,在彈出的快捷選單中選擇“在新視窗中開啟”時,子視窗便可以訪問父視窗的Session。

如果客戶端瀏覽器將Cookie功能禁用,或者不支援Cookie怎