java的會話管理:Cookie和Session
1.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就會自動再次發送到服務器端
2.Cookie技術
2.1.什么是Cookie
Cookie意為“甜餅”,是由W3C組織提出,最早由Netscape社區發展的一種機制。目前Cookie已經成為標準,所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支持Cookie。
由于HTTP是一種無狀態的協議,服務器單從網絡連接上無從知道客戶身份。怎么辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務器就能從通行證上確認客戶身份了。這就是Cookie的工作原理。
Cookie實際上是一小段的文本信息??蛻舳苏埱蠓掌?,如果服務器需要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie??蛻舳藶g覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,以此來辨認用戶狀態。服務器還可以根據需要修改Cookie的內容。
2.2 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做類似的處理。
2.3 Unicode編碼:保存中文
中文與英文字符不同,中文屬于Unicode字符,在內存中占4個字符,而英文屬于ASCII字符,內存中只占2個字節。Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼。
提示:Cookie中保存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因為瀏覽器不一定支持,而且JavaScript也不支持GBK編碼。
2.4.Cookie技術核心API
Cookie類:用于存儲會話數據。常用方法如下:
1.構造Cookie對象
Cookie(java.lang.String name, java.lang.String value)
2.設置cookie
void setPath(java.lang.String uri) :設置cookie的有效訪問路徑
void setMaxAge(int expiry) : 設置cookie的有效時間
void setValue(java.lang.String newValue) :設置cookie的值
3.發送cookie到瀏覽器端保存
void response.addCookie(Cookie cookie) : 發送cookie
4.服務器端接收cookie
Cookie[] request.getCookies() : 接收cookie
代碼示例:
/**
* 測試Cookie的方法
*/
@WebServlet(name = "CookieDemo")
public class CookieDemo extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.創建Cookie對象
/**
* 在cookie保存對象的實現方式:可以采用JSON
* 將該對象轉換為一個JSON字符串 需要對轉化之后的json字符串實行編碼 保存在cookie中當需要該信息的時候
* 可以從cookie中獲取這個json字符串(需要解碼) 然后 把他重新轉換為我們的對象 這樣就可已使用了
* 如:URLEncoder.encode(userStr,"utf-8") URLDecoder.decode(cook.getValue(),"utf-8")
*
*/
Cookie cookie = new Cookie("name","eric");
//2.設置Cookie參數
//2.1.設置Cookie的有效路徑
cookie.setPath("/hello");//默認就是web項目的地址
//2.2.設置Cookie的有效時間
cookie.setMaxAge(20);//該cookie只存活20秒,從最后不調該cookie開始計算
cookie.setMaxAge(-1);//該cookie保存在瀏覽器內存中,關閉瀏覽器則銷毀該cookie
cookie.setMaxAge(0);//刪除根該cookie同名的cookie
//3.把數據發送到瀏覽器
response.addCookie(cookie);
//4.服務端接收來自瀏覽器的cookie
//方法1:
// String name = request.getHeader("cookie");
// System.out.println(name);
//方法2:
Cookie[] cookies = request.getCookies();
//注意:判斷null,否則空指針
if(cookies!=null){
//遍歷
for(Cookie c:cookies){
String name = c.getName();
String value = c.getValue();
System.out.println(name+"="+value);
}
}else{
System.out.println("沒有接收cookie數據");
}
}
}
2.5.Cookie原理
1.服務器創建Cookie對象,把會話數據存儲到Cookie對象中
new Cookie("name","value");
2.服務器發送cookie信息到瀏覽器
response.addCookie(cookie);
其實是隱藏發送了一個set-cookie名稱的響應頭
3.瀏覽器得到服務器發送的cookie,然后保存在瀏覽器端
4.瀏覽器在下次訪問服務器時,會帶著cookie信息
包含在http的請求頭里
5.服務器接收到瀏覽器帶來的cookie信息
request.getCookies();
2.6.Cookie的細節
1.void setPath(java.lang.String uri)
:設置Cookie的有效訪問路徑,有效訪問路徑指的是Cookie的有效路徑保存在哪里,那么瀏覽器在有效路徑下訪問服務器的時候就會帶著Cookie信息,否則不帶Cookie信息,默認是在當前web項目的路徑下
2.void setMaxAge(int expiry)
:設置Cookie的有效時間
expiry可以是正整數,負整數,和零
正整數:表示Cookie數據保存到瀏覽器緩存所在的硬盤中,數值表示保存的時間
負整數:表示Cookie數據保存到瀏覽器的內存中,瀏覽器關閉Cookie就丟失了
零:表示刪除同名的Cookie數據
3.Cookie數據類型只能保存非中文字符串類型的??梢员4娑鄠€Cookie,但是瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制為4KB。
2.7.Cookie案例:顯示用戶上次訪問的時間
功能實現邏輯:
將時間保存在Cookie中,每次訪問從Cookie里面調用
第一次訪問:
1.獲取當前時間,顯示到瀏覽器中
2.創建Cookie對象,時間作為cookie值,名為:lastTime
3.把cookie發送到瀏覽器保存
第N次訪問:
1.獲取cookie的數據,取出名為lastTime的cookie
2.得到cookie的值(上次訪問時間)
3.顯示上次訪問時間到瀏覽器中
4.更新名為lastTime的cookie。值設置為當前時間
5.把更新后的cookie發送給瀏覽器保存
代碼實現:
/**
* 案例-用戶上次訪問的時間
*/
@WebServlet("/last")
public class HistServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//獲取當前時間
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String curTime = format.format(new Date());
//取得cookie
Cookie[] cookies = request.getCookies();
String lastTime = null;
//第n次訪問
if(cookies!=null){
for (Cookie cookie : cookies) {
if(cookie.getName().equals("lastTime")){
//有lastTime的cookie,已經是第n次訪問
lastTime = cookie.getValue();//上次訪問的時間
//第n次訪問
//1.把上次顯示時間顯示到瀏覽器
response.getWriter().write("歡迎回來,你上次訪問的時間為:"+lastTime+",當前時間為:"+curTime);
//2.更新cookie
cookie.setValue(curTime);
cookie.setMaxAge(1*30*24*60*60);
//3.把更新后的cookie發送到瀏覽器
response.addCookie(cookie);
break;
}
}
}
/**
* 第一次訪問(沒有cookie 或 有cookie,但沒有名為lastTime的cookie)
*/
if(cookies==null || lastTime==null){
//1.顯示當前時間到瀏覽器
response.getWriter().write("你是首次訪問本網站,當前時間為:"+curTime);
//2.創建Cookie對象
Cookie cookie = new Cookie("lastTime",curTime);
cookie.setMaxAge(1*30*24*60*60);//保存一個月
//3.把cookie發送到瀏覽器保存
response.addCookie(cookie);
}
}
}
2.8.Cookie案例:查看用戶瀏覽過的商品
邏輯圖
這個項目的代碼較多,按照功能的不同放在不同的包里面
公司域名的倒寫+項目名字+功能名字
cenyu.hist.entity 存放實體對象
cenyu.hist.dao Data Access Object 數據訪問對象,主要存放實體對象的一些方法(CRUD-create,read,update,delete)
cenyu.hist.servlet 存放servlet程序
cenyu.hist.ytil 存放工具類
cenyu.hist.test 存放測試類
等等
寫的順序:實體對象-->DAO類-->Servlet程序
代碼:暫無
3.Session技術
3.1.什么是Session
Session是服務器端技術,利用這個技術,服務器在運行時可以為每一個用戶的數據創建一個其獨享的Session對象,由于Session為用戶瀏覽器獨享,所以當用戶在訪問服務器的web資源時,可以把各自的數據放在各自的Session中,當用戶再去訪問服務器中的其他的web資源的時候,其他的web資源再從用戶各自的Session中取出數據為用戶服務
3.2.Session的核心技術
Session的類是HttpSession類:用于保存會話數據
1.創建或得到session對象
HttpSession getSession()
直接創建一個Session對象
HttpSession getSession(boolean create)
接收布爾值,設置為true時,在沒有找到匹配Session編號的對象時新建一個Sessionu對象。如果設置false,則當找不到匹配的Session時,返回null,建議不要用
2.設置session對象
void setMaxInactiveInterval(int interval)
: 設置session的有效時間
java.lang.String getId()
: 得到session編號
void invalidate()
: 銷毀session對象
Session對象的方法:
1.setMaxInactiveInterval方法默認30分鐘自動回收Session對象
2.使用setMaxInactiveInterval方法修改銷毀時間
3.在web.xml文件中全局修改Session默認回收時間
<!-- 修改session全局有效時間:分鐘 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
4.通過invalidate方法,手動銷毀Session對象
3.保存會話數據到session對象
void setAttribute(java.lang.String name, java.lang.Object value) : 保存數據
java.lang.Object getAttribute(java.lang.String name) : 獲取數據
void removeAttribute(java.lang.String name) : 清除數據
4.如何避免瀏覽器的JSESSIONID的cookie隨著瀏覽器關閉而丟失的問題:
解決方法是手動發送一個硬盤保護的cookie給瀏覽器
代碼見案例:
/**
* 測試Session的方法
*/
@WebServlet(name = "SessionServletDemo")
public class SessionServletDemo extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.創建Session對象
HttpSession session = request.getSession();
//2.保存會話數據
session.setAttribute("name","eric");
//3.取出會話數據
String name = (String)session.getAttribute("name");
System.out.println(name);
//4.
//拿到Session的id
System.out.println(session.getId());
//修改Session有效時間
session.setMaxInactiveInterval(20);
//銷毀session
if (session!=null){
session.invalidate();
}
//手動發送一個硬盤保存的cookie給瀏覽器
Cookie c = new Cookie("JSESSION", session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
}
}
3.3.Session原理
代碼解讀:HttpSession session = request.getSession();
偽碼分析執行過程
1.第一次訪問創建Session對象,給Session對象分配一個唯一的ID,叫JSESSIONID。
new HttpSession();
2.把JSESSIONID作為Cookie的值發送給瀏覽器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
response.addCookie(cookie);
3.第二次訪問的時候,瀏覽器帶著JSESSIONID的cookie訪問服務器
4.服務器得到JSESSIONID,在服務器的內存中搜索是否存放對應編號的session對象
5.如果找到對應編號的session對象,直接返回該對象
6.如果找不到對應編號session對象,創建新的session對象,繼續走1的流程
結論通過JSESSION的cookie值在服務器找session對象
3.4.Session案例:用戶登錄效果
需求:實現用戶登錄效果,如果登錄成功顯示:歡迎回來,×××。如果失敗,顯示登錄失敗
使用Session區分不同的用戶來實現,整個代碼實現分為三塊,登錄表單提交之后的處理邏輯,登錄邏輯,登出邏輯:
默認登錄界面。index.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>登錄頁面</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="this is my page">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body>
<form action="/day12/LoginServlet" method="post">
用戶名:<input type="text" name="userName"/>
<br/>
密碼:<input type="text" name="userPwd"/>
<br/>
<input type="submit" value="登錄"/>
</form>
</body>
</html>
登錄失敗頁面:fail.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>信息提示頁面</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="this is my page">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
</head>
<body>
<font color='red' size='3'>親, 你的用戶名或密碼輸入有誤!請重新輸入!</font><br/>
<a href="/day12/login.html">返回登錄頁面</a>
</body>
</html>
表單提交之后的主處理邏輯:IndexServlet.java
/**
* 用戶主頁的邏輯
*
*/
public class IndexServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
String html = "";
/**
* 接收request域對象的數據
*/
/*
String loginName = (String)request.getAttribute("loginName");
*/
/**
* 二、在用戶主頁,判斷session不為空且存在指定的屬性才視為登錄成功!才能訪問資源。
* 從session域中獲取會話數據
*/
//1.得到session對象
HttpSession session = request.getSession(false);
if(session==null){
//沒有登錄成功,跳轉到登錄頁面
response.sendRedirect(request.getContextPath()+"/login.html");
return;
}
//2.取出會話數據
String loginName = (String)session.getAttribute("loginName");
if(loginName==null){
//沒有登錄成功,跳轉到登錄頁面
response.sendRedirect(request.getContextPath()+"/login.html");
return;
}
html = "<html><body>歡迎回來,"+loginName+",<a href='"+request.getContextPath()+"/LogoutServlet'>安全退出</a></body></html>";
writer.write(html);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
登錄處理邏輯:LoginServlet.java
/**
* 處理登錄的邏輯
*
*/
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1.接收參數
String userName = request.getParameter("userName");
String userPwd = request.getParameter("userPwd");
//2.判斷邏輯
if("eric".equals(userName)
&& "123456".equals(userPwd)){
//登錄成功
/**
* 分析:
* context域對象:不合適,可能會覆蓋數據。
* request域對象: 不合適,整個網站必須得使用轉發技術來跳轉頁面
* session域對象:合適。
*/
/*
request.setAttribute("loginName", userName);
//request.getRequestDispatcher("/IndexServlet").forward(request, response);
response.sendRedirect(request.getContextPath()+"/IndexServlet");
*/
/**
* 一、登錄成功后,把用戶數據保存session對象中
*/
//1.創建session對象
HttpSession session = request.getSession();
//2.把數據保存到session域中
session.setAttribute("loginName", userName);
//3.跳轉到用戶主頁
response.sendRedirect(request.getContextPath()+"/IndexServlet");
}else{
//登錄失敗
//請求重定向
response.sendRedirect(request.getContextPath()+"/fail.html");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
退出處理邏輯:LogoutServlet.java
/**
* 退出邏輯
*
*/
public class LogoutServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 三、安全退出:
* 刪除掉session對象中指定的loginName屬性即可!
*/
//1.得到session對象
HttpSession session = request.getSession(false);
if(session!=null){
//2.刪除屬性
session.removeAttribute("loginName");
}
//2.回來登錄頁面
response.sendRedirect(request.getContextPath()+"/login.html");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}