會話(Session)跟蹤是Web程序中常用的技術(shù)敌蜂,用來跟蹤用戶的整個會話。常用的會話跟蹤技術(shù)是Cookie與Session。
Cookie通過在客戶端記錄信息確定用戶身份喂分,Session通過在服務(wù)器端記錄信息確定用戶身份聚假。
1.1 Cookie機制
在程序中块蚌,會話跟蹤是很重要的事情。理論上膘格,一個用戶的所有請求操作都應(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)该肴,當再次購買商品時服務(wù)器已經(jīng)無法判斷該購買行為是屬于用戶A的會話還是用戶B的會話了情竹。要跟蹤該會話,必須引入一種機制匀哄。
Cookie就是這樣的一種機制秦效。它可以彌補HTTP協(xié)議無狀態(tài)的不足。在Session出現(xiàn)之前涎嚼,基本上所有的網(wǎng)站都采用Cookie來跟蹤會話阱州。
1.1.1 什么是Cookie
Cookie意為“甜餅”,是由W3C組織提出
由于HTTP是一種無狀態(tài)的協(xié)議铸抑,服務(wù)器單從網(wǎng)絡(luò)連接上無從知道客戶身份贡耽。怎么辦呢?就給客戶端們頒發(fā)一個通行證吧鹊汛,每人一個蒲赂,無論誰訪問都必須攜帶自己通行證。這樣服務(wù)器就能從通行證上確認客戶身份了刁憋。這就是Cookie的工作原理滥嘴。
Cookie實際上是一小段的文本信息≈脸埽客戶端請求服務(wù)器若皱,如果服務(wù)器需要記錄該用戶狀態(tài),就使用response向客 戶端瀏覽器頒發(fā)一個Cookie尘颓∽叽ィ客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求該網(wǎng)站時疤苹,瀏覽器把請求的網(wǎng)址連同該Cookie一同提交給服務(wù) 器互广。服務(wù)器檢查該Cookie,以此來辨認用戶狀態(tài)。服務(wù)器還可以根據(jù)需要修改Cookie的內(nèi)容惫皱。
注意:Cookie功能需要瀏覽器的支持像樊。
如果瀏覽器不支持Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效旅敷。
不同的瀏覽器采用不同的方式保存Cookie生棍。
1.1.2 記錄用戶訪問次數(shù)
Java中把Cookie封裝成了javax.servlet.http.Cookie類。每個Cookie都是該Cookie類的對象媳谁。服務(wù)器通過操作Cookie類對象對客戶端Cookie進行操作涂滴。通過request.getCookie()獲取客戶端提交的所有Cookie
(以Cookie[]數(shù)組形式返回),通過response.addCookie(Cookiecookie)向客戶端設(shè)置Cookie韩脑。
Cookie對象使用key-value屬性對的形式保存用戶狀態(tài)氢妈,一個Cookie對象保存一個屬性對,一個request或者response同時使用多個Cookie段多。因為Cookie類位于包javax.servlet.http.*下面,所以JSP中不需要import該類壮吩。
1.1.3 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做類似的處理。
1.1.4 Unicode編碼:保存中文
中文與英文字符不同葱峡,中文屬于Unicode字符砚哗,在內(nèi)存中占4個字符,而英文屬于ASCII字符砰奕,內(nèi)存中只占2個字節(jié)蛛芥。Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼军援。
提示:Cookie中保存中文只能編碼仅淑。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼胸哥,因為瀏覽器不一定支持涯竟,而且JavaScript也不支持GBK編碼。
1.1.5 BASE64編碼:保存二進制圖片
Cookie不僅可以使用ASCII字符與Unicode字符空厌,還可以使用二進制數(shù)據(jù)庐船。例如在Cookie中使用數(shù)字證書,提供安全度嘲更。使用二進制數(shù)據(jù)時也需要進行編碼筐钟。
注意:本程序僅用于展示Cookie中可以存儲二進制內(nèi)容,并不實用赋朦。由于瀏覽器每次請求服務(wù)器都會攜帶Cookie篓冲,因此Cookie內(nèi)容不宜過多,否則影響速度宠哄。Cookie的內(nèi)容應(yīng)該少而精壹将。
1.1.6 設(shè)置Cookie的所有屬性
JavaScript 可以使用 document.cookie 屬性來創(chuàng)建 、讀取琳拨、及刪除 cookie瞭恰。
//寫cookies
document.cookie = "username1=xiaoming;"
您還可以為 cookie 添加一個過期時間(以 UTC 或 GMT 時間)。默認情況下狱庇,cookie 在瀏覽器關(guān)閉時刪除:
//設(shè)置過期時間
document.cookie = "username2=xiaoming; expires=Thu, 18 Dec 2019 12:00:00 GMT";
您可以使用 path 參數(shù)告訴瀏覽器 cookie 的路徑惊畏。默認情況下,cookie 屬于當前頁面密任。
document.cookie = "username3=xiaoming; expires=Thu, 18 Dec 2019 12:00:00 GMT;path=/";
//讀取 Cookie
// 在 JavaScript 中, 可以使用以下代碼來讀取 cookie:
var x = document.cookie;
console.log(x, typeof x);
document.cookie 將以字符串的方式返回所有的 cookie颜启,類型格式: cookie1=value; cookie2=value; cookie3=value;
修改 Cookie
//在 JavaScript 中,修改 cookie 類似于創(chuàng)建 cookie浪讳,如下所示:
document.cookie="username3=xiaohong; expires=Thu, 18 Dec 2019 12:00:00 GMT; path=/";
舊的 cookie 將被覆蓋缰盏。
//刪除 Cookie
//刪除 cookie 非常簡單。您只需要設(shè)置 expires 參數(shù)為以前的時間即可,如下所示口猜,設(shè)置為 Thu, 01 Jan 1970 00:00:00 GMT:
document.cookie = "username3=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
注意负溪,當您刪除時不必指定 cookie 的值。
完整函數(shù)示例
//設(shè)置 cookie 值的函數(shù)
首先济炎,我們創(chuàng)建一個函數(shù)用于存儲訪問者的名字:
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires;
}
以上的函數(shù)參數(shù)中川抡,cookie 的名稱為 cname,cookie 的值為 cvalue须尚,并設(shè)置了 cookie 的過期時間 expires崖堤。
該函數(shù)設(shè)置了 cookie 名、cookie 值耐床、cookie過期時間密幔。
//獲取 cookie 值的函數(shù)
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
}
return "";
}
cookie 名的參數(shù)為 cname。
創(chuàng)建一個文本變量用于檢索指定 cookie :cname + "="撩轰。
使用分號來分割 document.cookie 字符串胯甩,并將分割后的字符串數(shù)組賦值給 ca (ca = document.cookie.split(';'))。
循環(huán) ca 數(shù)組 (i=0;i<ca.length;i++)钧敞,然后讀取數(shù)組中的每個值蜡豹,并去除前后空格 (c=ca[i].trim())。
如果找到 cookie(c.indexOf(name) == 0)溉苛,返回 cookie 的值 (c.substring(name.length,c.length)。
如果沒有找到 cookie, 返回 ""弄诲。
//檢測 cookie 值的函數(shù)
最后愚战,我們可以創(chuàng)建一個檢測 cookie 是否創(chuàng)建的函數(shù)。
如果設(shè)置了 cookie齐遵,將顯示一個問候信息寂玲。
如果沒有設(shè)置 cookie,將會顯示一個彈窗用于詢問訪問者的名字梗摇,并調(diào)用 setCookie 函數(shù)將訪問者的名字存儲 365 天:
function checkCookie() {
var username = getCookie("username");
if (username != "") {
alert("Welcome again " + username);
}
else {
username = prompt("Please enter your name:", "");
if (username != "" && username != null) {
setCookie("username", username, 365);
}
}
}
1.2 Session機制
除了使用Cookie拓哟,Web應(yīng)用程序中還經(jīng)常使用Session來記錄客戶端狀態(tài)。Session是服務(wù)器端使用的一種記錄客戶端狀態(tài)的機制伶授,
使用上比Cookie簡單一些断序,相應(yīng)的也增加了服務(wù)器的存儲壓力。
1.2.1 什么是Session
Session是另一種記錄客戶狀態(tài)的機制糜烹,不同的是Cookie保存在客戶端瀏覽器中违诗,而Session保存在服務(wù)器上〈模客戶端瀏覽器訪問服務(wù)器的時候诸迟,服務(wù)器把客戶端信息以某種形式記錄在服務(wù)器上。這就是Session≌笪客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態(tài)就可以了壁公。
如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那么Session機制就是通過檢查服務(wù)器上的“客戶明細表”來確認客戶身份绅项。Session相當于程序在服務(wù)器上建立的一份客戶檔案紊册,客戶來訪的時候只需要查詢客戶檔案表就可以了。
1.2.2 實現(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隱藏 對象线衫,可以直接使用。如果使用聲明了<%@page session="false" %>惑折,則Session隱藏對象不可用授账。
當多個客戶端執(zhí)行程序時,服務(wù)器會保存多個客戶端的Session惨驶。獲取Session的時候也不需要聲明獲取誰的Session白热。Session機制決定了當前客戶只會獲取到自己的Session,而不會獲取到別人的Session粗卜。各客戶的Session也彼此獨立屋确,互不可見。
提示:Session的使用比Cookie方便续扔,但是過多的Session存儲在服務(wù)器內(nèi)存中攻臀,會對服務(wù)器造成壓力。
1.2.3 Session的生命周期
Session保存在服務(wù)器端测砂。為了獲得更高的存取速度茵烈,服務(wù)器一般把Session放在內(nèi)存里。每個用戶都會有一個獨立的Session砌些。如果Session內(nèi)容過于復(fù)雜呜投,當大量客戶訪問服務(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ù)器都認為該用戶的Session“活躍(active)”了一次。
1.2.4 Session的有效期
由于會有越來越多的用戶訪問服務(wù)器煌恢,因此Session也會越來越多骇陈。為防止內(nèi)存溢出,服務(wù)器會把長時間內(nèi)沒有活躍的Session從內(nèi)存刪除瑰抵。這個時間就是Session的超時時間你雌。如果超過了超時時間沒訪問過服務(wù)器,Session就自動失效了二汛。
1.2.6 Session對瀏覽器的要求
雖然Session保存在服務(wù)器匪蝙,對客戶端是透明的,它的正常運行仍然需要客戶端瀏覽器的支持习贫。這是因為Session 需要使用Cookie作為識別標志。HTTP協(xié)議是無狀態(tài)的千元,Session不能依據(jù)HTTP連接來判斷是否為同一客戶苫昌,因此服務(wù)器向客戶端瀏覽器發(fā)送一 個名為JSESSIONID的Cookie,它的值為該Session的id(也就是HttpSession.getId()的返回值)幸海。Session 依據(jù)該Cookie來識別是否為同一用戶祟身。
該Cookie為服務(wù)器自動生成的,它的maxAge屬性一般為–1物独,表示僅當前瀏覽器內(nèi)有效袜硫,并且各瀏覽器窗口間不共享,關(guān)閉瀏覽器就會失效挡篓。
因此同一機器的兩個瀏覽器窗口訪問服務(wù)器時婉陷,會生成兩個不同的Session帚称。但是由瀏覽器窗口內(nèi)的鏈接、腳本等打開的新窗口(也就是說不是雙擊桌面瀏覽器圖標等打開的窗口)除外秽澳。這類子窗口會共享父窗口的Cookie闯睹,因此會共享一個Session。
注意:新開的瀏覽器窗口會生成新的Session担神,但子窗口除外楼吃。子窗口會共用父窗口的Session。例如妄讯,在鏈接上右擊孩锡,在彈出的快捷菜單中選擇“在新窗口中打開”時,子窗口便可以訪問父窗口的Session亥贸。
如果客戶端瀏覽器將Cookie功能禁用躬窜,或者不支持Cookie怎么辦?例如砌函,絕大多數(shù)的手機瀏覽器都不支持Cookie斩披。Java Web提供了另一種解決方案:URL地址重寫。
1.2.7 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>
<ahref="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。當?shù)诙卧L問時服務(wù)器已經(jīng)在瀏覽器中寫入Cookie了蝶缀,因此URL地址重寫后的地址中就不會帶有jsessionid了丹喻。
1.2.8 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)容如下:
代碼1.11 /META-INF/context.xml
<?xml version='1.0' encoding='UTF-8'?>
<Context path="/sessionWeb"cookies="false">
</Context>
或者修改Tomcat全局的conf/context.xml藏研,修改內(nèi)容如下:
代碼1.12 context.xml
<!-- The contents of this file will be loaded for eachweb application -->
<Context cookies="false">
<!-- ... 中間代碼略 -->
</Context>
部署后TOMCAT便不會自動生成名JSESSIONID的Cookie,Session也不會以Cookie為識別標志概行,而僅僅以重寫后的URL地址為識別標志了蠢挡。
注意:該配置只是禁止Session使用Cookie作為識別標志,并不能阻止其他的Cookie讀寫凳忙。也就是說服務(wù)器不會自動維護名為JSESSIONID的Cookie了业踏,但是程序中仍然可以讀寫其他的Cookie鞍陨。
參考資料:
https://www.cnblogs.com/linguoguo/p/5106618.html
http://www.runoob.com/js/js-cookies.html