常用的會話跟蹤技術是Cookie與session。Cookie通過在客戶端記錄信息確定用戶身份,Session通過在服務器端記錄信息確定用戶身份。 Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務器的交互狀態。它們有各自的優點,也有各自的缺陷,然而具有諷刺意味的是它們的優點和它們的使用場景又是矛盾的。例如,使用 Cookie 來傳遞信息時,隨著 Cookie 個數的增多和訪問量的增加,它占用的網絡帶寬也很大,試想假如 Cookie 占用 200 個字節,如果一天的 PV 有幾億,它要占用多少帶寬?所以有大訪問量的時候希望用 Session,但是 Session 的致命弱點是不容易在多臺服務器之間共享,所以這也限制了 Session 的使用。
Cookie是瀏覽器(User Agent)訪問一些網站后,這些網站存放在客戶端的一組數據,用于使網站等跟蹤用戶,實現用戶自定義功能。通俗地說就是當一個用戶通過 HTTP 協議訪問一個服務器的時候,這個服務器會將一些 Key/Value 鍵值對返回給客戶端瀏覽器,并給這些數據加上一些限制條件,在條件符合時這個用戶下次訪問這個服務器的時候,數據又被完整地帶回給服務器。
Cookie實際上是一小段的文本信息??蛻舳苏埱蠓掌?,如果服務器需要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie??蛻舳藶g覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,以此來辨認用戶狀態。服務器還可以根據需要修改Cookie的內容。
在瀏覽器控制臺輸入javaScript:alert (document. cookie)就可以了(需要有網才能查看)。Javascript腳本會彈出一個對話框顯示本網站頒發的所有Cookie的內容。 注意:Cookie功能需要瀏覽器的支持。 如果瀏覽器不支持Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效。 不同的瀏覽器采用不同的方式保存Cookie。
(1)Java中把Cookie封裝成了javax.servlet.http.Cookie類。每個Cookie都是該Cookie類的對象。 (2)服務器通過操作Cookie類對象對客戶端Cookie進行操作。通過request.getCookie()獲取客戶端提交的所有Cookie(以Cookie[]數組形式返回),通過response.addCookie(Cookiecookie)向客戶端設置Cookie。 Cookie對象使用key-value屬性對的形式保存用戶狀態,一個Cookie對象保存一個屬性對,一個request或者response同時使用多個Cookie。因為Cookie類位于包javax.servlet.http.*下面,所以jsp中不需要import該類。
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做了特殊處理。
中文與英文字符不同,中文屬于Unicode字符,在內存中占4個字符,而英文屬于ASCII字符,內存中只占2個字節。Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼。
提示:Cookie中保存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因為瀏覽器不一定支持,而且JavaScript也不支持GBK編碼。
Cookie不僅可以使用ASCII字符與Unicode字符,還可以使用二進制數據。例如在Cookie中使用數字證書,提供安全度。使用二進制數據時也需要進行編碼。 注意:本程序僅用于展示Cookie中可以存儲二進制內容,并不實用。由于瀏覽器每次請求服務器都會攜帶Cookie,因此Cookie內容不宜過多,否則影響速度。Cookie的內容應該少而精。
除了name與value之外,Cookie還具有其他幾個常用的屬性。每個屬性對應一個getter方法與一個setter方法。
屬性名 | 描述 |
---|---|
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的maxAge決定著Cookie的有效期,單位為秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。
如果maxAge屬性為正數,則表示該Cookie會在maxAge秒之后自動失效。瀏覽器會將maxAge為正數的Cookie持久化,即寫到對應的Cookie文件中。無論客戶關閉了瀏覽器還是電腦,只要還在maxAge秒之前,登錄網站時該Cookie仍然有效。
Cookie cookie = new Cookie("username","hello world"); // 新建Cookiecookie.setMaxAge(Integer.MAX_VALUE); // 設置生命周期為MAX_VALUEresponse.addCookie(cookie); // 輸出到客戶端如果maxAge為負數,則表示該Cookie僅在本瀏覽器窗口以及本窗口打開的子窗口內有效,關閉窗口后該Cookie即失效。maxAge為負數的Cookie,為臨時性Cookie,不會被持久化,不會被寫到Cookie文件中。Cookie信息保存在瀏覽器內存中,因此關閉瀏覽器該Cookie就消失了。Cookie默認的maxAge值為–1。
如果maxAge為0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie的方法,因此通過設置該Cookie即時失效實現刪除Cookie的效果。失效的Cookie會被瀏覽器從Cookie文件或者內存中刪除。
Cookie cookie = new Cookie("username","hello world"); // 新建Cookiecookie.setMaxAge(0); // 設置生命周期為0,不能為負數response.addCookie(cookie); // 必須執行這一句response對象提供的Cookie操作方法只有一個添加操作add(Cookie cookie)。 要想修改Cookie只能使用一個同名的Cookie來覆蓋原來的Cookie,達到修改的目的。刪除時只需要把maxAge修改為0即可。
注意:從客戶端讀取Cookie時,包括maxAge在內的其他屬性都是不可讀的,也不會被提交。瀏覽器提交Cookie時只會提交name與value屬性。maxAge屬性只被瀏覽器用來判斷Cookie是否過期。
Cookie并不提供修改、刪除操作。如果要修改某個Cookie,只需要新建一個同名的Cookie,添加到response中覆蓋原來的Cookie。 如果要刪除某個Cookie,只需要新建一個同名的Cookie,并將maxAge設置為0,并添加到response中覆蓋原來的Cookie。注意是0而不是負數。負數代表其他的意義。
注意:修改、刪除Cookie時,新建的Cookie除value、maxAge之外的所有屬性,例如name、path、domain等,都要與原Cookie完全一樣。否則,瀏覽器將視為兩個不同的Cookie不予覆蓋,導致修改、刪除失敗。
domain屬性決定運行訪問Cookie的域名,而path屬性決定允許訪問Cookie的路徑(ContextPath)。例如,如果只允許/session/下的程序使用Cookie,可以這么寫:
Cookie cookie = new Cookie("time","20170302"); // 新建Cookiecookie.setPath("/session/"); // 設置路徑response.addCookie(cookie); // 輸出到客戶端注意:頁面只能獲取它屬于的Path的Cookie。例如/session/test/a.jsp不能獲取到路徑為/session/abc/的Cookie。使用時一定要注意。
HTTP協議不僅是無狀態的,而且是不安全的。使用HTTP協議的數據不經過任何加密就直接在網絡上傳播,有被截獲的可能。使用HTTP協議傳輸很機密的內容是一種隱患。如果不希望Cookie在HTTP等非安全協議中傳輸,可以設置Cookie的secure屬性為true。瀏覽器只會在HTTPS和SSL等安全協議中傳輸此類Cookie。下面的代碼設置secure屬性為true:
Cookie cookie = new Cookie("time", "20170302"); // 新建Cookiecookie.setSecure(true); // 設置安全屬性response.addCookie(cookie); // 輸出到客戶端提示:secure屬性并不能對Cookie內容加密,因而不能保證絕對的安全性。如果需要高安全性,需要在程序中對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不會有任何結果。
Session是另一種記錄客戶狀態的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務器上??蛻舳藶g覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session。
Session 是存放在服務器端的類似于HashTable結構(每一種web開發技術的實現可能不一樣,下文直接稱之為HashTable)來存放用戶數據,當瀏覽器 第一次發送請求時,服務器自動生成了一個HashTable和一個Session ID用來唯一標識這個HashTable,并將其通過響應發送到瀏覽器。當瀏覽器第二次發送請求,會將前一次服務器響應中的Session ID放在請求中一并發送到服務器上,服務器從請求中提取出Session ID,并和保存的所有Session ID進行對比,找到這個用戶對應的HashTable。
一般情況下,服務器會在一定時間內(默認20分鐘)保存這個 HashTable,過了時間限制,就會銷毀這個HashTable。在銷毀之前,程序員可以將用戶的一些數據以Key和Value的形式暫時存放在這個 HashTable中。當然,也有使用數據庫將這個HashTable序列化后保存起來的,這樣的好處是沒了時間的限制,壞處是隨著時間的增加,這個數據 庫會急速膨脹,特別是訪問量增加的時候。一般還是采取前一種方式,以減輕服務器壓力。
Session對應的類為javax.servlet.http.HttpSession類。每個來訪者對應一個Session對象,所有該客戶的狀態信息都保存在這個Session對象里。Session對象是在客戶端第一次請求服務器的時候創建的。Session也是一種key-value的屬性對,通過getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法讀寫客戶狀態信息。Servlet里通過request.getSession()方法獲取該客戶的Session。
HttpSession session = request.getSession(); // 獲取Session對象session.setAttribute("loginTime", new Date()); // 設置Session中的屬性提示:Session的使用比Cookie方便,但是過多的Session存儲在服務器內存中,會對服務器造成壓力。
Session在用戶第一次訪問服務器的時候自動創建。需要注意只有訪問JSP、Servlet等程序時才會創建Session,只訪問HTML、IMAGE等靜態資源并不會創建Session。如果尚未生成Session,也可以使用request.getSession(true)強制生成Session。 Session生成后,只要用戶繼續訪問,服務器就會更新Session的最后訪問時間,并維護該Session。用戶每訪問服務器一次,無論是否讀寫Session,服務器都認為該用戶的Session“活躍(active)”了一次。
由于會有越來越多的用戶訪問服務器,因此Session也會越來越多。為防止內存溢出,服務器會把長時間內沒有活躍的Session從內存刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過服務器,Session就自動失效了。 Session的超時時間為maxInactiveInterval屬性,可以通過對應的getMaxInactiveInterval()獲取,通過setMaxInactiveInterval(longinterval)修改。 Session的超時時間也可以在web.xml中修改。另外,通過調用Session的invalidate()方法可以使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)修改超時時間??梢孕薷膚eb.xml改變Session的默認超時時間。例如修改為60分鐘:
<session-config> <session-timeout>60</session-timeout> <!-- 單位:分鐘 --></session-config>注意:<session-timeout>參數的單位為分鐘,而setMaxInactiveInterval(int s)單位為秒。
雖然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怎么辦?例如,絕大多數的手機瀏覽器都不支持Cookie。Java Web提供了另一種解決方案:URL地址重寫。
URL地址重寫是對客戶端不支持Cookie的解決方案。URL地址重寫的原理是將該用戶Session的id信息重寫到URL地址中。服務器能夠解析重寫后的URL獲取Session的id。這樣即使客戶端不支持Cookie,也可以使用Session來記錄用戶狀態。HttpServletResponse類提供了encodeURL(Stringurl)實現URL地址重寫,例如:
<td> <a href="<%=response.encodeURL("index.jsp?c=1&wd=test") %>">Homepage</a></td>該方法會自動判斷客戶端是否支持Cookie。如果客戶端支持Cookie,會將URL原封不動地輸出來。如果客戶端不支持Cookie,則會將用戶Session的id重寫到URL中。重寫后的輸出可能是這樣的:
<td> <a href="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=1&wd=test">Homepage</a></td>即在文件名的后面,在URL參數的前面添加了字符串“;jsessionid=XXX”。其中XXX為Session的id。分析一下可以知道,增添的jsessionid字符串既不會影響請求的文件名,也不會影響提交的地址欄參數。用戶單擊這個鏈接的時候會把Session的id通過URL提交到服務器上,服務器通過解析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的依據是請求中是否含有Cookie。盡管客戶端可能會支持Cookie,但是由于第一次請求時不會攜帶任何Cookie(因為并無任何Cookie可以攜帶),URL地址重寫后的地址中仍然會帶有jsessionid。當第二次訪問時服務器已經在瀏覽器中寫入Cookie了,因此URL地址重寫后的地址中就不會帶有jsessionid了。
既然WAP上大部分的客戶瀏覽器都不支持Cookie,索性禁止Session使用Cookie,統一使用URL地址重寫會更好一些。Java Web規范支持通過配置的方式禁用Cookie。下面舉例說一下怎樣通過配置禁止使用Cookie。 打開項目sessionWeb的WebRoot目錄下的META-INF文件夾(跟WEB-INF文件夾同級,如果沒有則創建),打開context.xml(如果沒有則創建),編輯內容如下:
<?xml version='1.0' encoding='UTF-8'?><Context path="/sessionWeb"cookies="false"></Context>或者修改Tomcat全局的conf/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讀寫。也就是說服務器不會自動維護名為JSESSIONID的Cookie了,但是程序中仍然可以讀寫其他的Cookie。
參考文章: 深入理解 Session 與 Cookie Cookie/Session機制詳解 認識cookie與session的區別與應用 看完就徹底懂了session和cookie
新聞熱點
疑難解答