深入理解Cookie和Session機制

目錄

Cookie機制
什么是Cookie
Cookie的不可跨域名性
Unicode編碼:保存中文
BASE64編碼:保存二進制圖片
設(shè)置Cookie的所有屬性
Cookie的有效期
Cookie的修改、刪除
Cookie的域名
Cookie的路徑
Cookie的安全屬性
JavaScript操作Cookie
案例:永久登錄
Session機制
什么是Session
實現(xiàn)用戶登錄
Session的生命周期
Session的有效期
Session的常用方法
Session對瀏覽器的要求
URL地址重寫
Session中禁止使用Cookie
Cookie與Session的區(qū)別

轉(zhuǎn)載: 理解Cookie和Session機制

會話(Session)跟蹤是Web程序中常用的技術(shù),用來跟蹤用戶的整個會話。常用的會話跟蹤技術(shù)是Cookie與Session。Cookie通過在客戶端記錄信息確定用戶身份,Session通過在服務(wù)器端記錄信息確定用戶身份。

本章將系統(tǒng)地講述Cookie與Session機制,并比較說明什么時候不能用Cookie,什么時候不能用Session。

Cookie機制

Cookie技術(shù)是客戶端的解決方案,Cookie就是由服務(wù)器發(fā)給客戶端的特殊信息,而這些信息以文本文件的方式存放在客戶端,然后客戶端每次向服務(wù)器發(fā)送請求的時候都會帶上這些特殊的信息。讓我們說得更具體一些:當(dāng)用戶使用瀏覽器訪問一個支持Cookie的網(wǎng)站的時候,用戶會提供包括用戶名在內(nèi)的個人信息并且提交至服務(wù)器;接著,服務(wù)器在向客戶端回傳相應(yīng)的超文本的同時也會發(fā)回這些個人信息,當(dāng)然這些信息并不是存放在HTTP響應(yīng)體(Response Body)中的,而是存放于HTTP響應(yīng)頭(Response Header);當(dāng)客戶端瀏覽器接收到來自服務(wù)器的響應(yīng)之后,瀏覽器會將這些信息存放在一個統(tǒng)一的位置,對于Windows操作系統(tǒng)而言,我們可以從: [系統(tǒng)盤]:\Documents and Settings[用戶名]\Cookies目錄中找到存儲的Cookie;自此,客戶端再向服務(wù)器發(fā)送請求的時候,都會把相應(yīng)的Cookie再次發(fā)回至服務(wù)器。而這次,Cookie信息則存放在HTTP請求頭(Request Header)了。有了Cookie這樣的技術(shù)實現(xiàn),服務(wù)器在接收到來自客戶端瀏覽器的請求之后,就能夠通過分析存放于請求頭的Cookie得到客戶端特有的信息,從而動態(tài)生成與該客戶端相對應(yīng)的內(nèi)容。通常,我們可以從很多網(wǎng)站的登錄界面中看到“請記住我”這樣的選項,如果你勾選了它之后再登錄,那么在下一次訪問該網(wǎng)站的時候就不需要進行重復(fù)而繁瑣的登錄動作了,而這個功能就是通過Cookie實現(xiàn)的。

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

而Web應(yīng)用程序是使用HTTP協(xié)議傳輸數(shù)據(jù)的。HTTP協(xié)議是無狀態(tài)的協(xié)議。一旦數(shù)據(jù)交換完畢,客戶端與服務(wù)器端的連接就會關(guān)閉,再次交換數(shù)據(jù)需要建立新的連接。這就意味著服務(wù)器無法從連接上跟蹤會話。即用戶A購買了一件商品放入購物車內(nèi),當(dāng)再次購買商品時服務(wù)器已經(jīng)無法判斷該購買行為是屬于用戶A的會話還是用戶B的會話了。要跟蹤該會話,必須引入一種機制。

Cookie就是這樣的一種機制。它可以彌補HTTP協(xié)議無狀態(tài)的不足。在Session出現(xiàn)之前,基本上所有的網(wǎng)站都采用Cookie來跟蹤會話。

如果你把Cookies看成為http協(xié)議的一個擴展的話,理解起來就容易的多了,其實本質(zhì)上cookies就是http的一個擴展。有兩個http頭部是專門負(fù)責(zé)設(shè)置以及發(fā)送cookie的,它們分別是Set-Cookie以及Cookie。當(dāng)服務(wù)器返回給客戶端一個http響應(yīng)信息時,其中如果包含Set-Cookie這個頭部時,意思就是指示客戶端建立一個cookie,并且在后續(xù)的http請求中自動發(fā)送這個cookie到服務(wù)器端,直到這個cookie過期。如果cookie的生存時間是整個會話期間的話,那么瀏覽器會將cookie保存在內(nèi)存中,瀏覽器關(guān)閉時就會自動清除這個cookie。另外一種情況就是保存在客戶端的硬盤中,瀏覽器關(guān)閉的話,該cookie也不會被清除,下次打開瀏覽器訪問對應(yīng)網(wǎng)站時,這個cookie就會自動再次發(fā)送到服務(wù)器端。一個cookie的設(shè)置以及發(fā)送過程分為以下四步:

客戶端發(fā)送一個http請求到服務(wù)器端 服務(wù)器端發(fā)送一個http響應(yīng)到客戶端,其中包含Set-Cookie頭部 客戶端發(fā)送一個http請求到服務(wù)器端,其中包含Cookie頭部 服務(wù)器端發(fā)送一個http響應(yīng)到客戶端

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

image

在客戶端的第二次請求中包含的Cookie頭部中,提供給了服務(wù)器端可以用來唯一標(biāo)識客戶端身份的信息。這時,服務(wù)器端也就可以判斷客戶端是否啟用了cookies。盡管,用戶可能在和應(yīng)用程序交互的過程中突然禁用cookies的使用,但是,這個情況基本是不太可能發(fā)生的,所以可以不加以考慮,這在實踐中也被證明是對的。

除了cookies,客戶端還可以將發(fā)送給服務(wù)器的數(shù)據(jù)包含在請求的url中,比如請求的參數(shù)或者請求的路徑中。 我們來看一個常規(guī)的http get 請求例子:

GET /index.php?foo=bar HTTP/1.1 Host: example.org

另外一種客戶端傳遞數(shù)據(jù)到服務(wù)器端的方式是將數(shù)據(jù)包含在http請求的內(nèi)容區(qū)域內(nèi)。 這種方式需要請求的類型是POST的,看下面一個例子:

POST /index.php HTTP/1.1 Host: example.org Content-Type: application/x-www-form-urlencoded Content-Length: 7

foo=bar

在一個請求中,可以同時包含這兩種形式的數(shù)據(jù):

POST /index.php?myget=foo HTTP/1.1 Host: example.orgContent-Type: application/x-www-form-urlencoded Content-Length: 11

mypost=bar

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

GET /index.php?PHPSESSID=12345 HTTP/1.1 Host: example.org

什么是Cookie

Cookie意為“甜餅”,是由W3C組織提出,最早由Netscape社區(qū)發(fā)展的一種機制。目前Cookie已經(jīng)成為標(biāo)準(zhǔn),所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支持Cookie。

由于HTTP是一種無狀態(tài)的協(xié)議,服務(wù)器單從網(wǎng)絡(luò)連接上無從知道客戶身份。怎么辦呢?就給客戶端們頒發(fā)一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務(wù)器就能從通行證上確認(rèn)客戶身份了。這就是Cookie的工作原理。

Cookie實際上是一小段的文本信息。客戶端請求服務(wù)器,如果服務(wù)器需要記錄該用戶狀態(tài),就使用response向客戶端瀏覽器頒發(fā)一個Cookie。客戶端瀏覽器會把Cookie保存起來。當(dāng)瀏覽器再請求該網(wǎng)站時,瀏覽器把請求的網(wǎng)址連同該Cookie一同提交給服務(wù)器。服務(wù)器檢查該Cookie,以此來辨認(rèn)用戶狀態(tài)。服務(wù)器還可以根據(jù)需要修改Cookie的內(nèi)容。

image

查看某個網(wǎng)站頒發(fā)的Cookie很簡單。在瀏覽器地址欄輸入javascript:alert (document. cookie)就可以了(需要有網(wǎng)才能查看)。JavaScript腳本會彈出一個對話框顯示本網(wǎng)站頒發(fā)的所有Cookie的內(nèi)容,如圖所示。

image

上圖中彈出的對話框中顯示的為Baidu網(wǎng)站的Cookie。其中第一行BAIDUID記錄的就是筆者的身份helloweenvsfei,只是Baidu使用特殊的方法將Cookie信息加密了。

注意:Cookie功能需要瀏覽器的支持。如果瀏覽器不支持Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效。不同的瀏覽器采用不同的方式保存Cookie。IE瀏覽器會在“C:\Documents and Settings\你的用戶名\Cookies”文件夾下以文本文件形式保存,一個文本文件保存一個Cookie。

記錄用戶訪問次數(shù)

Java中把Cookie封裝成了javax.servlet.http.Cookie類。每個Cookie都是該Cookie類的對象。服務(wù)器通過操作Cookie類對象對客戶端Cookie進行操作。通過request.getCookie()獲取客戶端提交的所有Cookie(以Cookie[]數(shù)組形式返回),通過response.addCookie(Cookie cookie)向客戶端設(shè)置Cookie。

Cookie對象使用key-value屬性對的形式保存用戶狀態(tài),一個Cookie對象保存一個屬性對,一個request或者response同時使用多個Cookie。因為Cookie類位于包javax.servlet.http.*下面,所以JSP中不需要import該類。

Cookie的不可跨域名性

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

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

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

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

注意:用戶登錄網(wǎng)站www.google.com之后會發(fā)現(xiàn)訪問images.google.com時登錄信息仍然有效,而普通的Cookie是做不到的。這是因為Google做了特殊處理。本章后面也會對Cookie做類似的處理。

Unicode編碼:保存中文

中文與英文字符不同,中文屬于Unicode字符,在內(nèi)存中占4個字符,而英文屬于ASCII字符,內(nèi)存中只占2個字節(jié)。Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼。

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

BASE64編碼:保存二進制圖片

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

注意:本程序僅用于展示Cookie中可以存儲二進制內(nèi)容,并不實用。由于瀏覽器每次請求服務(wù)器都會攜帶Cookie,因此Cookie內(nèi)容不宜過多,否則影響速度。Cookie的內(nèi)容應(yīng)該少而精。

設(shè)置Cookie的所有屬性

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

String name:該Cookie的名稱。Cookie一旦創(chuàng)建,名稱便不可更改。 Object value:該Cookie的值。如果值為Unicode字符,需要為字符編碼。如果值為二進制數(shù)據(jù),則需要使用BASE64編碼。 int maxAge:該Cookie失效的時間,單位秒。如果為正數(shù),則該Cookie在>maxAge秒之后失效。如果為負(fù)數(shù),該Cookie為臨時Cookie,關(guān)閉瀏覽器即失效,瀏覽器也不會以任何形式保存該Cookie。如果為0,表示刪除該Cookie。默認(rèn)為–1。 boolean secure:該Cookie是否僅被使用安全協(xié)議傳輸。安全協(xié)議。安全協(xié)議有HTTPS,SSL等,在網(wǎng)絡(luò)>上傳輸數(shù)據(jù)之前先將數(shù)據(jù)加密。默認(rèn)為false。 String path:該Cookie的使用路徑。如果設(shè)置為“/sessionWeb/”,則只有contextPath為“/sessionWeb”的程序可以訪問該Cookie。如果設(shè)置為“/”,則本域名下contextPath都可以訪問該Cookie。注意最后一個字符必須為“/”。 >String domain:可以訪問該Cookie的域名。如果設(shè)置為“.google.com”,則所有以“google.com”結(jié)尾的域名都可以訪問該Cookie。注意第一個字符必須為“.”。 String comment:該Cookie的用處說明。瀏覽器顯示Cookie信息的時候顯示該說明。 int version:該Cookie使>用的版本號。0表示遵循Netscape的Cookie規(guī)范,1表示遵循W3C的RFC 2109規(guī)范。

Cookie的有效期

Cookie的maxAge決定著Cookie的有效期,單位為秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。 如果maxAge屬性為正數(shù),則表示該Cookie會在maxAge秒之后自動失效。瀏覽器會將maxAge為正數(shù)的Cookie持久化,即寫到對應(yīng)的Cookie文件中。無論客戶關(guān)閉了瀏覽器還是電腦,只要還在maxAge秒之前,登錄網(wǎng)站時該Cookie仍然有效。下面代碼中的Cookie信息將永遠有效。

 `Cookie cookie =` `new` `Cookie(``"username"``,``"helloweenvsfei"``);` `// 新建Cookie`

`cookie.setMaxAge(Integer.MAX_VALUE);` `// 設(shè)置生命周期為MAX_VALUE`

`response.addCookie(cookie);` `// 輸出到客戶端`

如果maxAge為負(fù)數(shù),則表示該Cookie僅在本瀏覽器窗口以及本窗口打開的子窗口內(nèi)有效,關(guān)閉窗口后該Cookie即失效。maxAge為負(fù)數(shù)的Cookie,為臨時性Cookie,不會被持久化,不會被寫到Cookie文件中。Cookie信息保存在瀏覽器內(nèi)存中,因此關(guān)閉瀏覽器該Cookie就消失了。Cookie默認(rèn)的maxAge值為–1。

如果maxAge為0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie的方法,因此通過設(shè)置該Cookie即時失效實現(xiàn)刪除Cookie的效果。失效的Cookie會被瀏覽器從Cookie文件或者內(nèi)存中刪除:

 `Cookie cookie =` `new` `Cookie(``"username"``,``"helloweenvsfei"``);` `// 新建Cookie`

`cookie.setMaxAge(``0``);` `// 設(shè)置生命周期為0,不能為負(fù)數(shù)`

`response.addCookie(cookie);` `// 必須執(zhí)行這一句`

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

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

Cookie的修改、刪除

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

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

Cookie的域名

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

正常情況下,同一個一級域名下的兩個二級域名如www.helloweenvsfei.comimages.helloweenvsfei.com也不能交互使用Cookie,因為二者的域名并不嚴(yán)格相同。如果想所有helloweenvsfei.com名下的二級域名都可以使用該Cookie,需要設(shè)置Cookie的domain參數(shù),例如:

`Cookie cookie =` `new` `Cookie(``"time"``,``"20080808"``);` `// 新建Cookie`

`cookie.setDomain(``".helloweenvsfei.com"``);` `// 設(shè)置域名`

`cookie.setPath(``"/"``);` `// 設(shè)置路徑`

`cookie.setMaxAge(Integer.MAX_VALUE);` `// 設(shè)置有效期`

`response.addCookie(cookie);` `// 輸出到客戶端`

讀者可以修改本機C:\WINDOWS\system32\drivers\etc下的hosts文件來配置多個臨時域名,然后使用setCookie.jsp程序來設(shè)置跨域名Cookie驗證domain屬性。

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

Cookie的路徑

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

`Cookie cookie =` `new` `Cookie(``"time"``,``"20080808"``);` `// 新建Cookie`

`cookie.setPath(``"/session/"``);` `// 設(shè)置路徑`

`response.addCookie(cookie);` `// 輸出到客戶端`

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

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

  1. domain表示的是cookie所在的域,默認(rèn)為請求的地址,如網(wǎng)址為www.test.com/test/test.aspx,那么domain默認(rèn)為www.test.com。而跨域訪問,如域A為t1.test.com,域B為t2.test.com,那么在域A生產(chǎn)一個令域A和域B都能訪問的cookie就要將該cookie的domain設(shè)置為.test.com;如果要在域A生產(chǎn)一個令域A不能訪問而域B能訪問的cookie就要將該cookie的domain設(shè)置為t2.test.com

  2. path表示cookie所在的目錄,默認(rèn)為/,就是根目錄。在同一個服務(wù)器上有目錄如下:/test/,/test/cd/,/test/dd/,現(xiàn)設(shè)一個cookie1的path為/test/,cookie2的path為/test/cd/,那么test下的所有頁面都可以訪問到cookie1,而/test/和/test/dd/的子頁面不能訪問cookie2。這是因為cookie能讓其path路徑下的頁面訪問。

  3. 瀏覽器會將domain和path都相同的cookie保存在一個文件里,cookie間用*隔開。

Cookie的安全屬性

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

`Cookie cookie =` `new` `Cookie(``"time"``,` `"20080808"``);` `// 新建Cookie`

`cookie.setSecure(``true``);` `// 設(shè)置安全屬性`

`response.addCookie(cookie);` `// 輸出到客戶端`

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

JavaScript操作Cookie

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

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

由于JavaScript能夠任意地讀寫Cookie,有些好事者便想使用JavaScript程序去窺探用戶在其他網(wǎng)站的Cookie。不過這是徒勞的,W3C組織早就意識到JavaScript對Cookie的讀寫所帶來的安全隱患并加以防備了,W3C標(biāo)準(zhǔn)的瀏覽器會阻止JavaScript讀寫任何不屬于自己網(wǎng)站的Cookie。換句話說,A網(wǎng)站的JavaScript程序讀寫B(tài)網(wǎng)站的Cookie不會有任何結(jié)果。

案例:永久登錄

如果用戶是在自己家的電腦上上網(wǎng),登錄時就可以記住他的登錄信息,下次訪問時不需要再次登錄,直接訪問即可。實現(xiàn)方法是把登錄信息如賬號、密碼等保存在Cookie中,并控制Cookie的有效期,下次訪問時再驗證Cookie中的登錄信息即可。

保存登錄信息有多種方案。最直接的是把用戶名與密碼都保持到Cookie中,下次訪問時檢查Cookie中的用戶名與密碼,與數(shù)據(jù)庫比較。這是一種比較危險的選擇,一般不把密碼等重要信息保存到Cookie中。

還有一種方案是把密碼加密后保存到Cookie中,下次訪問時解密并與數(shù)據(jù)庫比較。這種方案略微安全一些。如果不希望保存密碼,還可以把登錄的時間戳保存到Cookie與數(shù)據(jù)庫中,到時只驗證用戶名與登錄時間戳就可以了。

這幾種方案驗證賬號時都要查詢數(shù)據(jù)庫。

本例將采用另一種方案,只在登錄時查詢一次數(shù)據(jù)庫,以后訪問驗證登錄信息時不再查詢數(shù)據(jù)庫。實現(xiàn)方式是把賬號按照一定的規(guī)則加密后,連同賬號一塊保存到Cookie中。下次訪問時只需要判斷賬號的加密規(guī)則是否正確即可。本例把賬號保存到名為account的Cookie中,把賬號連同密鑰用MD1算法加密后保存到名為ssid的Cookie中。驗證時驗證Cookie中的賬號與密鑰加密后是否與Cookie中的ssid相等。相關(guān)代碼如下: loginCookie.jsp:

<%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %>
<%!                                                  // JSP方法
    private static final String KEY =":cookie@helloweenvsfei.com"; // 密鑰 
    
    public final static String calcMD1(String ss) { // MD1 加密算法
       String s = ss == null ? "" : ss; // 若為null返回空
       char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; // 字典
       try {
           byte[] strTemp = s.getBytes();                          // 獲取字節(jié)
           MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 獲取MD1
           mdTemp.update(strTemp);                                // 更新數(shù)據(jù)
           byte[] md =mdTemp.digest();                        // 加密
           int j =md.length;                                 // 加密后的長度
           char str[] = new char[j * 2];                       // 新字符串?dāng)?shù)組
           int k =0;                                         // 計數(shù)器k
           for (int i = 0; i< j; i++) {                       // 循環(huán)輸出
               byte byte0 = md[i];
               str[k++] = hexDigits[byte0 >>> 4 & 0xf];
               str[k++] = hexDigits[byte0 & 0xf];
           }
           return new String(str);                             // 加密后字符串
       } catch (Exception e){return null; }
    }
%>
<%
   request.setCharacterEncoding("UTF-8");          // 設(shè)置request編碼
   response.setCharacterEncoding("UTF-8");        // 設(shè)置response編碼
   
   String action =request.getParameter("action"); // 獲取action參數(shù)
   
   if("login".equals(action)) {                       // 如果為login動作
        String account =request.getParameter("account"); // 獲取account參數(shù)
        String password =request.getParameter("password"); // 獲取password參數(shù)
        int timeout = new Integer(request.getParameter("timeout")); // 獲取timeout參數(shù)
        
        String ssid =calcMD1(account + KEY); // 把賬號、密鑰使用MD1加密后保存
        
        Cookie accountCookie = new Cookie("account", account); // 新建Cookie
        accountCookie.setMaxAge(timeout);              // 設(shè)置有效期
       
        Cookie ssidCookie =new Cookie("ssid", ssid);   // 新建Cookie
       ssidCookie.setMaxAge(timeout);                 // 設(shè)置有效期
       
       response.addCookie(accountCookie);             // 輸出到客戶端
       response.addCookie(ssidCookie);            // 輸出到客戶端
       
       // 重新請求本頁面,參數(shù)中帶有時間戳,禁止瀏覽器緩存頁面內(nèi)容
       response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
       return;
    } else if("logout".equals(action)) {                  // 如果為logout動作
       CookieaccountCookie = new Cookie("account", ""); // 新建Cookie,內(nèi)容為空
       accountCookie.setMaxAge(0); // 設(shè)置有效期為0,刪除
              
       Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,內(nèi)容為空
       ssidCookie.setMaxAge(0);                   // 設(shè)置有效期為0,刪除
       response.addCookie(accountCookie);         // 輸出到客戶端
       response.addCookie(ssidCookie);         // 輸出到客戶端
       // 重新請求本頁面,參數(shù)中帶有時間戳,禁止瀏覽器緩存頁面內(nèi)容
       response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
       return;
    }
    boolean login = false;                        // 是否登錄
    String account = null;                        // 賬號
    String ssid = null;                           // SSID標(biāo)識
   
    if(request.getCookies() !=null) {               // 如果Cookie不為空
        for(Cookie cookie : request.getCookies()) {  // 遍歷Cookie
           if(cookie.getName().equals("account"))  // 如果Cookie名為 account
               account = cookie.getValue();       // 保存account內(nèi)容
           if(cookie.getName().equals("ssid")) // 如果為SSID
               ssid = cookie.getValue();          // 保存SSID內(nèi)容
        }
    }
    if(account != null && ssid !=null) {    // 如果account、SSID都不為空
        login = ssid.equals(calcMD1(account + KEY)); // 如果加密規(guī)則正確, 則視為已經(jīng)登錄
    }
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
       <legend><%= login ? "歡迎您回來" : "請先登錄"%></legend>
        <% if(login){%>
            歡迎您, ${cookie.account.value }. &nbsp;&nbsp;
           <a href="${pageContext.request.requestURI }?action=logout">
            注銷</a>
            <% } else { %>
            <form action="${ pageContext.request.requestURI }?action=login" method="post">
               <table>
                   <tr><td>賬號: </td>
                       <td><input type="text"name="account" style="width: 200px; "></td>
                   </tr>
                    <tr>
                        <td>密碼: </td>
                       <td><inputtype="password" name="password"></td>
                   </tr>
                   <tr>
                       <td>有效期: </td>
                       <td>
                            <inputtype="radio" name="timeout" value="-1"checked> 關(guān)閉瀏覽器即失效 <br/>
                            <input type="radio"  name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天內(nèi)有效<br/>          
                             <input type="radio" name="timeout" value=  "<%= Integer.MAX_VALUE %>"> 永久有效<br/> 
                        </td>
                   </tr>
                   <tr>
                       <td><input type="submit"value=" 登  錄 " class=  "button"></td>
                   </tr>
           </table>
        </form>
        <% } %>

登錄時可以選擇登錄信息的有效期:關(guān)閉瀏覽器即失效、30天內(nèi)有效與永久有效。通過設(shè)置Cookie的age屬性來實現(xiàn),注意觀察代碼。運行效果如圖1.7所示。

image

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


Session機制

除了使用Cookie,Web應(yīng)用程序中還經(jīng)常使用Session來記錄客戶端狀態(tài)。Session是服務(wù)器端使用的一種記錄客戶端狀態(tài)的機制,使用上比Cookie簡單一些,相應(yīng)的也增加了服務(wù)器的存儲壓力。

Session技術(shù)則是服務(wù)端的解決方案,它是通過服務(wù)器來保持狀態(tài)的。由于Session這個詞匯包含的語義很多,因此需要在這里明確一下 Session的含義。首先,我們通常都會把Session翻譯成會話,因此我們可以把客戶端瀏覽器與服務(wù)器之間一系列交互的動作稱為一個 Session。從這個語義出發(fā),我們會提到Session持續(xù)的時間,會提到在Session過程中進行了什么操作等等;其次,Session指的是服務(wù)器端為客戶端所開辟的存儲空間,在其中保存的信息就是用于保持狀態(tài)。從這個語義出發(fā),我們則會提到往Session中存放什么內(nèi)容,如何根據(jù)鍵值從 Session中獲取匹配的內(nèi)容等。要使用Session,第一步當(dāng)然是創(chuàng)建Session了。那么Session在何時創(chuàng)建呢?當(dāng)然還是在服務(wù)器端程序運行的過程中創(chuàng)建的,不同語言實現(xiàn)的應(yīng)用程序有不同創(chuàng)建Session的方法,而在Java中是通過調(diào)用HttpServletRequest的getSession方法(使用true作為參數(shù))創(chuàng)建的。在創(chuàng)建了Session的同時,服務(wù)器會為該Session生成唯一的Session id,而這個Session id在隨后的請求中會被用來重新獲得已經(jīng)創(chuàng)建的Session;在Session被創(chuàng)建之后,就可以調(diào)用Session相關(guān)的方法往Session中增加內(nèi)容了,而這些內(nèi)容只會保存在服務(wù)器中,發(fā)到客戶端的只有Session id;當(dāng)客戶端再次發(fā)送請求的時候,會將這個Session id帶上,服務(wù)器接受到請求之后就會依據(jù)Session id找到相應(yīng)的Session,從而再次使用之。正式這樣一個過程,用戶的狀態(tài)也就得以保持了。

什么是Session

Session是另一種記錄客戶狀態(tài)的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務(wù)器上。客戶端瀏覽器訪問服務(wù)器的時候,服務(wù)器把客戶端信息以某種形式記錄在服務(wù)器上。這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態(tài)就可以了。

如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那么Session機制就是通過檢查服務(wù)器上的“客戶明細表”來確認(rèn)客戶身份。Session相當(dāng)于程序在服務(wù)器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。

實現(xiàn)用戶登錄

Session對應(yīng)的類為javax.servlet.http.HttpSession類。每個來訪者對應(yīng)一個Session對象,所有該客戶的狀態(tài)信息都保存在這個Session對象里。Session對象是在客戶端第一次請求服務(wù)器的時候創(chuàng)建的。Session也是一種key-value的屬性對,通過getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法讀寫客戶狀態(tài)信息。Servlet里通過request.getSession()方法獲取該客戶的Session,例如:

HttpSession session = request.getSession(); // 獲取Session對象
session.setAttribute("loginTime", new Date()); // 設(shè)置Session中的屬性
out.println("登錄時間為:" +(Date)session.getAttribute("loginTime")); // 獲取Session屬性

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

Servlet中必須使用request來編程式獲取HttpSession對象,而JSP中內(nèi)置了Session隱藏對象,可以直接使用。如果使用聲明了<font color=#c7254e><%@page session="false" %>,則Session隱藏對象不可用。下面的例子使用Session記錄客戶賬號信息。 session.jsp:

<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.page import="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
   DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器
%>
<%
   response.setCharacterEncoding("UTF-8");        // 設(shè)置request編碼
   Person[] persons =
   {           
      // 基礎(chǔ)數(shù)據(jù),保存三個人的信息
       new Person("Liu Jinghua","password1", 34, dateFormat.parse
       ("1982-01-01")),
       new Person("Hello Kitty","hellokitty", 23, dateFormat.parse
       ("1984-02-21")),
       new Person("Garfield", "garfield_pass",23, dateFormat.parse
       ("1994-09-12"))
    };
  
   String message = "";                      // 要顯示的消息
   
   if(request.getMethod().equals("POST"))
   { 
       // 如果是POST登錄       
       for(Person person :persons)
       {           
          // 遍歷基礎(chǔ)數(shù)據(jù),驗證賬號、密碼
          // 如果用戶名正確且密碼正確
          if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password")))
          {              
              // 登錄成功,設(shè)置將用戶的信息以及登錄時間保存到Session
              session.setAttribute("person", person);                   // 保存登錄的Person
              session.setAttribute("loginTime", new Date());          // 保存登錄的時間              
              response.sendRedirect(request.getContextPath() + "/welcome.jsp");
              return;
           }
       }      
       message = "用戶名密碼不匹配,登錄失敗。";       // 登錄失敗
   }
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
<html>
   // ... HTML代碼為一個FORM表單,代碼略,請看隨書光盤
</html>

登錄界面驗證用戶登錄信息,如果登錄正確,就把用戶信息以及登錄時間保存進Session,然后轉(zhuǎn)到歡迎頁面welcome.jsp。welcome.jsp中從Session中獲取信息,并將用戶資料顯示出來。 welcome.jsp:

<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
    DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器
%>
<%
    Person person =(Person)session.getAttribute("person");                       // 獲取登錄的person
    Date loginTime =(Date)session.getAttribute("loginTime");                     // 獲取登錄時間
%>
    // ... 部分HTML代碼略
            <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>

程序運行效果如圖所示。

image

注意:程序中Session中直接保存了Person類對象與Date類對象,使用起來要比Cookie方便。當(dāng)多個客戶端執(zhí)行程序時,服務(wù)器會保存多個客戶端的Session。獲取Session的時候也不需要聲明獲取誰的Session。Session機制決定了當(dāng)前客戶只會獲取到自己的Session,而不會獲取到別人的Session。各客戶的Session也彼此獨立,互不可見。

提示:Session的使用比Cookie方便,但是過多的Session存儲在服務(wù)器內(nèi)存中,會對服務(wù)器造成壓力。

Session的生命周期

Session保存在服務(wù)器端。為了獲得更高的存取速度,服務(wù)器一般把Session放在內(nèi)存里。每個用戶都會有一個獨立的Session。如果Session內(nèi)容過于復(fù)雜,當(dāng)大量客戶訪問服務(wù)器時可能會導(dǎo)致內(nèi)存溢出。因此,Session里的信息應(yīng)該盡量精簡。

Session在用戶第一次訪問服務(wù)器的時候自動創(chuàng)建。需要注意只有訪問JSP、Servlet等程序時才會創(chuàng)建Session,只訪問HTML、IMAGE等靜態(tài)資源并不會創(chuàng)建Session。如果尚未生成Session,也可以使用request.getSession(true)強制生成Session。

Session生成后,只要用戶繼續(xù)訪問,服務(wù)器就會更新Session的最后訪問時間,并維護該Session。用戶每訪問服務(wù)器一次,無論是否讀寫Session,服務(wù)器都認(rèn)為該用戶的Session“活躍(active)”了一次。

Session的有效期

由于會有越來越多的用戶訪問服務(wù)器,因此Session也會越來越多。為防止內(nèi)存溢出,服務(wù)器會把長時間內(nèi)沒有活躍的Session從內(nèi)存刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過服務(wù)器,Session就自動失效了。

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

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

Session的常用方法

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

void setAttribute(String attribute, Object value):設(shè)置Session屬性。value參數(shù)可以為任何Java Object。通常為Java Bean。value信息不宜過大 String getAttribute(String attribute):返回Session屬性 Enumeration getAttributeNames():返回Session中存在的屬性名 >void removeAttribute(String attribute):移除Session屬性 String getId():返回Session的ID。該ID由服務(wù)器自動創(chuàng)建,不會重復(fù) long getCreationTime():返回Session的創(chuàng)建日期。返回類型為long,常被轉(zhuǎn)化為Date類型,例如:Date createTime = new Date(session.get >CreationTime()) long getLastAccessedTime():返回Session的最后活躍時間。返回類型為long int getMaxInactiveInterval():返回Session的超時時間。單位為秒。超過該時間沒有訪問,服務(wù)器認(rèn)為該Session失效 void setMaxInactiveInterval(int second):設(shè)置Session的>超時時間。單位為秒 void putValue(String attribute, Object value):不推薦的方法。已經(jīng)被setAttribute(String attribute, Object Value)替代 Object getValue(String attribute):不被推薦的方法。已經(jīng)被getAttribute(String attr)替代 boolean isNew():返回該Session是否是>新創(chuàng)建的 void invalidate():使該Session失效

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

<session-config>
   <session-timeout>60</session-timeout>      <!-- 單位:分鐘 -->
</session-config>

注意:<session-timeout style="margin: 0px; padding: 0px; box-sizing: border-box;">參數(shù)的單位為分鐘,而setMaxInactiveInterval(int s)單位為秒。</session-timeout>

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

<Context path="/livsorder" docBase="/home/httpd/html/livsorder" defaultSessionTimeOut="3600" isWARExpanded="true"
    isWARValidated="false" isInvokerEnabled="true"
    isWorkDirPersistent="false"/>

Session對瀏覽器的要求

雖然Session保存在服務(wù)器,對客戶端是透明的,它的正常運行仍然需要客戶端瀏覽器的支持。這是因為Session需要使用Cookie作為識別標(biāo)志。HTTP協(xié)議是無狀態(tài)的,Session不能依據(jù)HTTP連接來判斷是否為同一客戶,因此服務(wù)器向客戶端瀏覽器發(fā)送一個名為JSESSIONID的Cookie,它的值為該Session的id(也就是HttpSession.getId()的返回值)。Session依據(jù)該Cookie來識別是否為同一用戶。

該Cookie為服務(wù)器自動生成的,它的maxAge屬性一般為–1,表示僅當(dāng)前瀏覽器內(nèi)有效,并且各瀏覽器窗口間不共享,關(guān)閉瀏覽器就會失效。

因此同一機器的兩個瀏覽器窗口訪問服務(wù)器時,會生成兩個不同的Session。但是由瀏覽器窗口內(nèi)的鏈接、腳本等打開的新窗口(也就是說不是雙擊桌面瀏覽器圖標(biāo)等打開的窗口)除外。這類子窗口會共享父窗口的Cookie,因此會共享一個Session。

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

如果客戶端瀏覽器將Cookie功能禁用,或者不支持Cookie怎么辦?例如,絕大多數(shù)的手機瀏覽器都不支持Cookie。Java Web提供了另一種解決方案:URL地址重寫。

URL地址重寫

URL地址重寫是對客戶端不支持Cookie的解決方案。URL地址重寫的原理是將該用戶Session的id信息重寫到URL地址中。服務(wù)器能夠解析重寫后的URL獲取Session的id。這樣即使客戶端不支持Cookie,也可以使用Session來記錄用戶狀態(tài)。HttpServletResponse類提供了encodeURL(Stringurl)實現(xiàn)URL地址重寫,例如:

<td>
    <a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>"> 
    Homepage</a>
</td>

該方法會自動判斷客戶端是否支持Cookie。如果客戶端支持Cookie,會將URL原封不動地輸出來。如果客戶端不支持Cookie,則會將用戶Session的id重寫到URL中。重寫后的輸出可能是這樣的:

<td>
    <a href="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=1&wd=Java">Homepage</a>
</td>

即在文件名的后面,在URL參數(shù)的前面添加了字符串“;jsessionid=XXX”。其中XXX為Session的id。分析一下可以知道,增添的jsessionid字符串既不會影響請求的文件名,也不會影響提交的地址欄參數(shù)。用戶單擊這個鏈接的時候會把Session的id通過URL提交到服務(wù)器上,服務(wù)器通過解析URL地址獲得Session的id。

如果是頁面重定向(Redirection),URL地址重寫可以這樣寫:

<%
    if(“administrator”.equals(userName)) {
        response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”));
        return;
    }
%>

效果跟response.encodeURL(String url)是一樣的:如果客戶端支持Cookie,生成原URL地址,如果不支持Cookie,傳回重寫后的帶有jsessionid字符串的地址。

對于WAP程序,由于大部分的手機瀏覽器都不支持Cookie,WAP程序都會采用URL地址重寫來跟蹤用戶會話。

注意:TOMCAT判斷客戶端瀏覽器是否支持Cookie的依據(jù)是請求中是否含有Cookie。盡管客戶端可能會支持Cookie,但是由于第一次請求時不會攜帶任何Cookie(因為并無任何Cookie可以攜帶),URL地址重寫后的地址中仍然會帶有jsessionid。當(dāng)?shù)诙卧L問時服務(wù)器已經(jīng)在瀏覽器中寫入Cookie了,因此URL地址重寫后的地址中就不會帶有jsessionid了。

由于Cookie可以被人為的禁止,必須有其他機制以便在Cookie被禁止時仍然能夠把session id傳遞回服務(wù)器。經(jīng)常被使用的一種技術(shù)叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種: 一種是作為URL路徑的附加信息,表現(xiàn)形式為http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764 一種是作為查詢字符串附加在URL后面,表現(xiàn)形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

這兩種方式對于用戶來說是沒有區(qū)別的,只是服務(wù)器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數(shù)區(qū)分開來。為了在整個交互過程中始終保持狀態(tài),就必須在每個客戶端可能請求的路徑后面都包含這個session id。

另一種技術(shù)叫做表單隱藏字段。就是服務(wù)器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session id傳遞回服務(wù)器。比如下面的表單:

<form name="testform" action="/xxx">
    <input type="text">
</form>

在被傳遞給客戶端之前將被改寫成:

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

這種技術(shù)現(xiàn)在已較少應(yīng)用。

在談?wù)搒ession機制的時候,常常聽到這樣一種誤解“只要關(guān)閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務(wù)器刪除一個session,否則服務(wù)器會一直保留,程序一般都是在用戶做log off的時候發(fā)個指令去刪除session。然而瀏覽器從來不會主動在關(guān)閉之前通知服務(wù)器它將要關(guān)閉,因此服務(wù)器根本不會有機會知道瀏覽器已經(jīng)關(guān)閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關(guān)閉瀏覽器后這個 session id就消失了,再次連接服務(wù)器時也就無法找到原來的session。如果服務(wù)器設(shè)置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發(fā)出的HTTP請求頭,把原來的session id發(fā)送給服務(wù)器,則再次打開瀏覽器仍然能夠找到原來的session。

恰恰是由于關(guān)閉瀏覽器不會導(dǎo)致session被刪除,迫使服務(wù)器為seesion設(shè)置了一個失效時間,當(dāng)距離客戶端上一次使用session的時間超過這個失效時間時,服務(wù)器就可以認(rèn)為客戶端已經(jīng)停止了活動,才會把session刪除以節(jié)省存儲空間。

Session中禁止使用Cookie

既然WAP上大部分的客戶瀏覽器都不支持Cookie,索性禁止Session使用Cookie,統(tǒng)一使用URL地址重寫會更好一些。Java Web規(guī)范支持通過配置的方式禁用Cookie。下面舉例說一下怎樣通過配置禁止使用Cookie。

打開項目sessionWeb的WebRoot目錄下的META-INF文件夾(跟WEB-INF文件夾同級,如果沒有則創(chuàng)建),打開context.xml(如果沒有則創(chuàng)建),編輯內(nèi)容如下: /META-INF/context.xml:

<?xml version='1.0' encoding='UTF-8'?>
    <Context path="/sessionWeb"cookies="false">
</Context>

或者修改Tomcat全局的conf/context.xml,修改內(nèi)容如下: context.xml:

<!-- The contents of this file will be loaded for eachweb application -->
<Context cookies="false">
    <!-- ... 中間代碼略 -->
</Context>

部署后TOMCAT便不會自動生成名JSESSIONID的Cookie,Session也不會以Cookie為識別標(biāo)志,而僅僅以重寫后的URL地址為識別標(biāo)志了

注意:該配置只是禁止Session使用Cookie作為識別標(biāo)志,并不能阻止其他的Cookie讀寫。也就是說服務(wù)器不會自動維護名為JSESSIONID的Cookie了,但是程序中仍然可以讀寫其他的Cookie。

Cookie與Session的區(qū)別

  1. cookie數(shù)據(jù)存放在客戶的瀏覽器上,session數(shù)據(jù)放在服務(wù)器上;
  2. cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙,考慮到安全應(yīng)當(dāng)使用session;
  3. session會在一定時間內(nèi)保存在服務(wù)器上。當(dāng)訪問增多,會比較占用你服務(wù)器的性能。考慮到減輕服務(wù)器性能方面,應(yīng)當(dāng)使用COOKIE;
  4. 單個cookie在客戶端的限制是3K,就是說一個站點在客戶端存放的COOKIE不能超過3K;

Cookie和Session的方案雖然分別屬于客戶端和服務(wù)端,但是服務(wù)端的session的實現(xiàn)對客戶端的cookie有依賴關(guān)系的,上面我講到服務(wù)端執(zhí)行session機制時候會生成session的id值,這個id值會發(fā)送給客戶端,客戶端每次請求都會把這個id值放到http請求的頭部發(fā)送給服務(wù)端,而這個id值在客戶端會保存下來,保存的容器就是cookie,因此當(dāng)我們完全禁掉瀏覽器的cookie的時候,服務(wù)端的session也會不能正常使用(注意:有些資料說ASP解決這個問題,當(dāng)瀏覽器的cookie被禁掉,服務(wù)端的session任然可以正常使用,ASP我沒試驗過,但是對于網(wǎng)絡(luò)上很多用php和jsp編寫的網(wǎng)站,我發(fā)現(xiàn)禁掉cookie,網(wǎng)站的session都無法正常的訪問)。

轉(zhuǎn)載于https://www.cnblogs.com/andy-zhou/p/5360107.html 感謝作者Andy.Zhou

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 空杯心態(tài)…… 萬物為無物!不以物喜,不以己悲!淡看江湖路,清心普善咒!
    文軒雨蔚閱讀 291評論 0 0
  • 第十章 明道 子路問強。子曰:“南方之強與?北方之強與?抑而強與?寬柔以教,不報無道,南方之強也,君...
    e1131080bdc0閱讀 864評論 2 1
  • 一個查看歷史命令的使用率工具, 因為看到oh-my-zsh項目中的zsh_statsfunction 后有感仿照開...
    a5768599b114閱讀 1,368評論 0 0
  • 我家養(yǎng)了一盆 虎皮蘭,今天 我一站到虎皮蘭那盆植物面前,就發(fā)現(xiàn) 虎皮蘭 都比我高了。他那 全身的花紋 像玉石一樣漂...
    王大勛閱讀 367評論 0 0
  • 你發(fā)來的信息,我讀了好多遍,依然覺得很開心 我發(fā)出的文字,修改了好多遍,依然覺得表達不夠 你只知道我喜歡你 卻不知...
    Jessica_jun閱讀 345評論 0 1