會話技術之Cookie詳解

很早之前寫過一篇關於Cookie和Session的文章,那是2017年的事咯,當時還是個學生,技術也菜,對知識理解的也不深。恰巧有機會重新學習Java Web,今天就再次來簡單的聊一聊Cookie與Session。

資源分配圖

1.會話與會話技術

在日常生活中,我們撥打電話接通後到掛斷前,在這期間兩人的交流就是一個會話。Web應用中的會話類似生活中的打電話,用戶登錄(撥號)、一系列的請求和響應(交流)、用戶退出登錄(掛斷電話)。

資源分配圖

​我們在電話剛接通時(此手機無來電顯示功能),首先會來一句,你好,我是XXX。兩人互通暗號之後,確認了雙方的身份,之後的交流也都是基於前面的信息來進行交流的,直到兩人都完成了此次會晤的目的,掛斷電話,此次會話結束。

資源分配圖

​但是我們知道,客戶端和瀏覽器端通信使用的Http協議是無狀態的,即每次請求對於服務端來講都是一個新的請求,無法基於前面的信息進行交流,所以想象下,客戶端和服務端兩方的交流情形(每到第四步的時候就會拐回第一步)。

資源分配圖

​基於上面的兩個對比,我們也可以知道,Web應用需要一種可以保持前面信息(之前的對話)的技術,這就是會話技術客戶端和服務器端每次的交流都可以被追蹤,類似於電話裏兩人的交流,可以記住前面所說的話。

2.原有技術的不足

​在我們之前學習的過程中,我們知道Java Web應用中是有域對象的,如HttpServletRequest、ServletContext,下面我們來下看這個兩個域對象能否保存會話信息。

  1. HttpServletRequest:每次Http請求,Servlet容器就會創建一個ServletRequest對象,該對象中保存着此次請求的所傳遞的數據,並且還可以通過其綁定屬性,來傳遞一些數據。但是,每次請求都會創建一個全新的ServletRequest,其生命週期爲當前Http請求,如果會話過程中調用不同的接口,兩次請求無法通過ServletRequest來共享信息。
  2. ServletContext:HttpServletRequest存在的問題,對於ServletContext來說似乎不是問題,因爲ServletContext在整個應用中是全局共享的,因此調用不同的接口,是可以通過其來追蹤之前的信息的。但是,如果如果多個用戶同時來,似乎是個比較麻煩的事情。

​基於ServletContext遇到的多用戶問題,如果你用過redis做驗證碼或者其他緩存的話,其實多用戶對你來說並不是問題,我們可以在需要保存的信息對應的key前增加唯一表示,比如用戶的賬號,形成一個如lizishu-code的key,這樣即可解決多用戶的問題。

​當時這樣來做,我們需要每次請求是都需要攜帶我們的賬號信息,存在的問題主要有如下幾點:

  1. 安全問題,如果只驗證賬號名,很容易被攻擊,因此同時需要攜帶密碼;
  2. 客戶端如何保持賬號信息,總不能從登錄頁開始,每個頁面間的跳轉都攜帶着賬號和密碼;
  3. 效率低,每次請求服務器都會默認來一遍身份驗證;

​原有技術無法有效的解決問題怎麼辦?OMG,當時是提出一個新技術了,也就是我們要講的兩位主角,Cookie和Session。Cookie用來解決客戶端如何保存信息的問題,Session來解決多用戶問題,即每個客戶端會對應一個session,當前會話產生的信息可以保存在session中,使用Cookie和Session的關聯性來解決安全問題。下面我們具體的來講解。

3.Cookie的概念

​Cookie是會話技術的一種,主要用於將會話過程產生的數據保存到客戶端(瀏覽器),從而使客戶端每次和服務端之間可以更好的進行交互。

​我們通過一個生活中的例子來理解下Cookie的概念。在生活中,大家應該都有接觸過會員卡,比如理髮店、超市等等。我們辦理會員卡後,店家會給我們一張卡片,以後每次來消費時,只需出示這張卡片,就可以獲取到你的餘額、積分、消費記錄等信息,然後基於上面的信息來進行折扣的計算、積分的累加,餘額的扣減等操作。

​在Web應用中,Cookie的功能就類似於上面的例子中的會員卡,第一次請求時,會創建一個Cookie,當用戶再次訪問服務器時,就會攜帶上Cookie(會員卡),服務端也會根據處理結果,將一些信息放到Cookie中,以保存在客戶端。

資源分配圖

​上圖描述了Cookie在瀏覽器和服務器之間的傳輸過程。當用戶第一次訪問服務器時,服務器可以在響應信息(response)中增加Set-Cookie響應頭,將信息以Cookie爲載體發送給瀏覽器。瀏覽器接收到服務器發送來的Cookie信息,就會將他保存在瀏覽器的緩衝區內。這樣,當瀏覽器再次訪問服務器時,就會將Cookie放在請求消息中,Web服務器就可以通過request中的用戶信息來分辨此次請求是由哪個用戶發起的。

​從上面我們可以發現,服務器端可以通過HttpServletRsponse來向客戶端發送Cookie,那瀏覽器只能緩存服務器端發來的Cookie麼?答案當時是NO,瀏覽器也可以通過JS來創建Cookie對象,並將其存儲。下面我們分別來介紹這兩種方式。

4.服務器端的Cookie

​爲了封裝Cookie信息,Tomcat在Servlet Api中提供了一個Cookie類,其中提供了許多方法,讓我們可以方便快捷的創建Cookie和設置其屬性。

​首先我們來看下Cookie的構造方法,Cookie類只提供了一個構造方法,其源碼如下所示:

public Cookie(String name, String value) {
  if (name == null || name.length() == 0) {
    throw new IllegalArgumentException(
      lStrings.getString("err.cookie_name_blank"));
  }
  if (!isToken(name) ||
      name.equalsIgnoreCase("Comment") || // rfc2019
      name.equalsIgnoreCase("Discard") || // 2019++
      name.equalsIgnoreCase("Domain") ||
      name.equalsIgnoreCase("Expires") || // (old cookies)
      name.equalsIgnoreCase("Max-Age") || // rfc2019
      name.equalsIgnoreCase("Path") ||
      name.equalsIgnoreCase("Secure") ||
      name.equalsIgnoreCase("Version") ||
      name.startsWith("$")) {
    String errMsg = lStrings.getString("err.cookie_name_is_token");
    Object[] errArgs = new Object[1];
    errArgs[0] = name;
    errMsg = MessageFormat.format(errMsg, errArgs);
    throw new IllegalArgumentException(errMsg);
  }

  this.name = name;
  this.value = value;
}

​從上面我們可以看到,創建一個Cookie對象需要傳入兩個參數name,value,也是我們比較熟悉的Key-Value模式,並且name不能爲null或空字符串,不可等於保留的token名。需要注意的是,Cookie對象一旦創建,其name屬性就不可更改了,value可以被修改。

​下面我們來看下Cookie類提供的設置其屬性的方法:

資源分配圖

​下面我們對其幾個重要的屬性進行講解:

  • maxAge:表示此Cookie在客戶端的有效期,單位是秒。默認值爲-1,當整個瀏覽器關閉後,此Cookie失效;當maxAge值爲正數時,即此Cookie還剩餘多少秒的有效期;當值爲0時,此Cookie失效,瀏覽器進行刪除操作;
  • path:表示此Cookie對path下的所屬的目錄和子目錄有效,比如path設置爲/rest,放客戶端發起/rest/aaaServlet請求時,就會攜帶上此Cookie;如果想讓此Cookie對站點所有的目錄有效的話,可以設置爲/
  • domain:表示可以訪問此Cookie的域名,如果不設置的話,會根據當前請求url來設置;當我們手動設置時,可以採用以".“開頭來定義此Cookie更大的域名訪問範圍,比如設置domain爲”.csdn.net",則當訪問"https://lizishudd.blog.csdn.net/“時,一樣可以使用此”.csdn.net"下的cookie(當然這裏還需要Cookie的path同時滿足要求);

​下面我們來簡單的演示下服務器端是如何向客戶端(瀏覽器)發送Cookie對象的,我們新建一個CookieServlet,urlPattern默認,其中的doGet方法如下:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
  // 設置編碼方式
  request.setCharacterEncoding("utf-8");
  response.setContentType("text/html;charset=utf-8");

  Cookie cookie = new Cookie("name", "lizishu");
  //xx.localhost域名下的所有應用都可以使用此Cookie
  cookie.setPath("/");
  cookie.setDomain(".localhost");
  //有效期5min
  cookie.setMaxAge(300);
  response.addCookie(cookie);
}

​瀏覽器中輸入URL:http://localhost:8080/FirstProject/CookieServlet,訪問此Servelt後,我們打開Chrome的控制態F12->Application->Storage->Cookies->http://localhost:8080(根據你輸入的url來定),截圖如下:

資源分配圖

​在瀏覽器中存在Cookie後,我們隨便在瀏覽器中在進行一次請求,http://localhost:8080/FirstProject/xxxx,我們來看下其請求頭,因爲xxxx沒有對應的目錄,因此404,不過這個不是重點,重點是,此次請求中Request Headers中已經攜帶了瀏覽器中存儲的Cookie。

資源分配圖

​在服務器端如何獲取Cookie,可參考上一篇博文:https://blog.csdn.net/qq_34666857/article/details/104677407

5.瀏覽器端的Cookie

​Cookie的創建工作除了在服務器端,還可以在瀏覽器端通過JavaScript完成。下面我們來看下如何使用JavaScript來來創建Cookie和修改Cookie(這裏僅僅是爲了演示,因此使用原生的Js,Jquery等其他JS框架都有對Cookie的支持)。

​首先我們來看下JavaScript是如何操作Cookie的:

//創建一個Cookie,屬性默認
document.cookie="password=123456";
//創建一個Cookie,設置屬性:過期時間,path
document.cookie="attribute=pathDomain; expires=Thu, 14 Dec 2021 12:00:00 GMT; path=/";
//讀取Cookie,返回name1=value1;...;namen=valuen  形式的字符串
document.cookie;
//修改Cookie,重新創建一遍,name相同會覆蓋之前Cookie,修改了過期時間
document.cookie="attribute=pathDomain; expires=Thu, 14 Dec 2020 12:00:00 GMT; path=/";
//刪除Cookie,可以指定過期時間爲當前時間;注意:因爲過期時間以瀏覽器的服務器時間爲準,一般會有八小時時差
document.cookie="password=123; expires=" + new Date();

​上面即是我們的原生JS操作Cookie的示例代碼,我們將其在Chrome的控制檯中運行,執行過程如下圖所示:

資源分配圖

​我們來看下Application中的Cookie信息。

資源分配圖

​下面提供兩個簡單的Cookie操作的JS函數:

//創建Cookie,並設置有效期(單位天)
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;
}

//獲取對應Cookie的值,通過字符串截取的方式
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,過期時間提前1天,解決時差問題
function delCookie(cname)
{
  var d = new Date();
  d.setTime(d.getTime()-(24*60*60*1000));
  var expires = "expires=" + d.toGMTString();
  document.cookie = cname + "=; " + expires;
}

6.總結

​因爲篇幅問題,本文只對Cookie進行討論,下文會對Session進行詳細的討論。Cookie的出現讓瀏覽器保存會話信息變得非常方便,而瀏覽器發起Http請求時,會將所有當前請求可用的Cookie全部帶上,也大大的方便了程序員的開發工作。

參考閱讀:

  1. Servlet基礎之HttpServletRequest詳解
  2. Servlet基礎之HttpServletResponse詳解
  3. JavaScript Cookie

​又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。

​Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。

​有任何疑問,可以評論區留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章