深刻理解Session和Cookie的區別

Cookie簡介

Cookie意爲「甜餅」,是由W3C組織提出,最先由Netscape社區發展的一種機制。 目前Cookie已經成爲標準,全部的主流瀏覽器如IE、Netscape、Firefox、Opera等都支持Cookie。html

因爲HTTP是一種無狀態的協議,服務器單從網絡鏈接上無從知道客戶身份。 怎麼辦呢?就給客戶端們頒發一個通行證吧,每人一個,不管誰訪問都必須攜帶本身通行證。 這樣服務器就能從通行證上確認客戶身份了。這就是Cookie的工做原理。git

Cookie其實是一小段的文本信息。 客戶端請求服務器,若是服務器須要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie。 客戶端瀏覽器會把Cookie保存起來。 當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。 服務器檢查該Cookie,以此來辨認用戶狀態。 服務器還能夠根據須要修改Cookie的內容。github

Cookie機制

Cookie技術是客戶端的解決方案,Cookie就是由服務器發給客戶端的特殊信息,而這些信息以文本文件的方式存放在客戶端, 而後客戶端每次向服務器發送請求的時候都會帶上這些特殊的信息。web

具體過程以下:數據庫

  1. 用戶使用瀏覽器訪問一個支持Cookie的網站的時候,用戶會提供包括用戶名在內的我的信息而且提交至服務器;
  2. 服務器在向客戶端回傳相應的超文本的同時也會發回這些我的信息,固然這些信息並非存放在HTTP響應體 (Response Body)中的,而是存放於HTTP響應頭(Response Header)
  3. 客戶端瀏覽器接收到來自服務器的響應以後,瀏覽器會將這些信息存放在一個統一的位置。 對於Windows操做系統而言,咱們能夠從: [系統盤]:\Documents and Settings[用戶名]\Cookies目錄中找到存儲的Cookie;
  4. 客戶端再次向服務器發送請求的時候,都會把相應的Cookie再次發回至服務器。 而此次,Cookie信息則存放在HTTP請求頭(equest Header)了。

HTTP的Cookie機制

Web應用程序是使用HTTP協議傳輸數據的。HTTP協議是無狀態的協議。 一旦數據交換完畢,客戶端與服務器端的鏈接就會關閉,再次交換數據須要創建新的鏈接。 這就意味着服務器沒法從鏈接上跟蹤會話。 舉個例子,用戶A購買了一件商品放入購物車內, 當再次購買商品時服務器已經沒法判斷該購買行爲是屬於用戶A的會話仍是用戶B的會話了。 要跟蹤該會話,必須引入一種機制。跨域

Cookie就是這樣的一種機制。它能夠彌補HTTP協議無狀態的不足。 在Session出現以前,基本上全部的網站都採用Cookie來跟蹤會話瀏覽器

Set-Cookie和Cookie

兩個Http頭部和Cookie有關 : Set-Cookie和Cookie緩存

當服務器返回給客戶端一個Http響應信息時,其中若是包含Set-Cookie這個頭部,說明:安全

  1. 指示客戶端創建一個cookie
  2. 在後續的Http請求中自動發送這個cookie到服務器端,直到這個cookie過時。
  3. 若是cookie的生存時間是整個會話期間的話,那麼瀏覽器會將 cookie 保存在內存中, 瀏覽器關閉時就會自動清除這個cookie。
  4. 若是將 cookie 保存在客戶端的硬盤中,瀏覽器關閉的話,該 cookie 也不會被清除, 下次打開瀏覽器訪問對應網站時,這個cookie就會自動再次發送到服務器端。

一個cookie的設置以及發送過程分爲如下四步:bash

    1. 客戶端發送一個http請求到服務器端


      1. 服務器端發送一個http響應到客戶端,其中包含Set-Cookie頭部


        1. 客戶端發送一個http請求到服務器端,其中包含Cookie頭部


          1. 服務器端發送一個http響應到客戶端




          在客戶端的第二次請求中包含Cookie頭部,提供給了服務器端能夠用來惟一標識客戶端身份的信息。 這時,服務器端也就能夠判斷客戶端是否啓用了cookie。 儘管,用戶可能在和應用程序交互的過程當中忽然禁用cookie的使用, 可是,這個狀況基本是不太可能發生的,因此能夠不加以考慮,這在實踐中也被證實是對的。

          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的API
          new Cookie(String key,String value);
          String getName();//獲取cookie的key(名稱)
          String getValue();//獲取cookie的值
          void setMaxAge(int);//設置cookie在瀏覽器存活時間,單位:秒
          //若是設置成0:表示刪除高cookie(前提:路徑必須一致)
          void setPath(String path);//設置cookie的路徑
          //當咱們訪問的路徑中包含次cookie的path,纔會攜帶cookie
          //默認訪問路徑:訪問Servlet的路徑,從"/項目名稱"開始,到最後一個"/"結束。好比:/demo/a/b,默認路徑爲/demo/a
          //手動設置路徑:以"/項目名稱"開始,以"/"結尾複製代碼
          寫回瀏覽器
          response.addCookie(Cookie);複製代碼
          獲取cookie
          Cookie[] request.getCookies();複製代碼
          • 核心代碼:
          /**
           * 根據 cookie名稱獲取Cookie 的工具類
           */
          public class CookieUtils {
              public static Cookie getCookieByName(String name,Cookie[] cookies){
                  if(cookies != null){
                      for(Cookie cookie : cookies){
                          if(name.equals(cookie.getName())){
                              return cookie;
                          }
                      }
                  }
                  return null;
              }
          }
          public class RecordServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  //1.設置編碼
                  response.setContentType("text/html;charset=utf-8");
                  PrintWriter w = response.getWriter();
          
                  //2.獲取指定名稱的Cookie
                  Cookie cookie = CookieUtils.getCookieByName("record",request.getCookies());
          
                  //3.判斷cookie是否爲空;
                  // 若爲null,則說明是第一次訪問;
                  // 若不爲 null,則根據cookie顯示上一次的訪問時間
                  if(cookie == null){
                      w.write("這是您第一次訪問");
                  }else{
                      long lastTime= Long.parseLong(cookie.getValue());
                      w.write("您上次訪問的時間:"+ new Date(lastTime).toLocaleString());
                  }
          
                  //4.記錄當前訪問時間,而且該信息存入cookie中
                  Cookie c = new Cookie("record",System.currentTimeMillis()+"");
                  //設置cookie的有效期是 1 小時
                  c.setMaxAge(60*60);
                  response.addCookie(c);
              }
          
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  doPost(request,response);
              }
          }複製代碼

          Cookie案例 : 瀏覽記錄

          • 核心代碼1:記錄商品瀏覽記錄
          /**
           * 記錄商品瀏覽記錄,只展現3個商品
           */
          public class CategoryServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  //獲取當前訪問商品的id
                  String id = request.getParameter("id");
          
                  Cookie c = CookieUtils.getCookieByName("ids",request.getCookies());
          
                  //判斷該 cookie 是否爲空
                  String ids="";
                  if(c == null){
                      //若爲空,說明以前沒有訪問記錄
                      //將當前商品的id放入ids中
                      ids = id;
                  }else{
                      //若不爲空,獲取值。也就是以前瀏覽的商品編號,使用 "-"進行鏈接
                      ids = c.getValue();
          
                      //將 ids 經過"-"進行分割,而後存入list中,方便後續的操做
                      String[] categoryIds = ids.split("-");
                      LinkedList<String> categories = new LinkedList<>();
                      if(categories != null){
                          for(String categoryId : categoryIds){
                              categories.add(categoryId);
                          }
                      }
                      //判斷以前記錄中有無該商品
                      if(categories.contains(id)){
                          //如有,刪除原來的id,將當前的id放入前面
                          categories.remove(id);
                      }else{
                          // 若沒有
                          // 繼續判斷長度是否>=3
                          // 若>=3,移除最後一個,將當前的id放入最前面
                          // 若<3,直接將當前的id放入最前面.
                          if(categories.size() >= 3){
                              categories.removeLast();
                          }
                      }
                      //無論如何,id都是最新瀏覽的,直接加入到前面
                      categories.addFirst(id);
          
                      ids="";
                      for(String categoryId : categories){
                          ids += (categoryId + "-");
                      }
                      ids = ids.substring(0,ids.length()-1);
                  }
          
                  //建立cookie
                  c=new  Cookie("ids",ids);
                  //設置訪問路徑
                  c.setPath(request.getContextPath()+"/");
                  //設置存活時間
                  c.setMaxAge(60);
          
                  //寫回瀏覽器
                  response.addCookie(c);
          
                  //跳轉到指定的商品頁面上
                  response.sendRedirect(request.getContextPath()+"/category_info"+id+".htm");
              }
          
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  doPost(request,response);
              }
          }複製代碼
          • 顯示瀏覽記錄
          <ul style="list-style: none;">
              <%
                  //獲取指定名稱的cookie ids
                  Cookie c= CookieUtils.getCookieByName("ids", request.getCookies());
          
                  //判斷ids是否爲空
                  if(c==null){
              %>
              <h2>暫無瀏覽記錄</h2>
              <%
              }else{//ids=3-2-1
                  String[] arr=c.getValue().split("-");
                  for(String id:arr){
              %>
              <li style="width: 150px;height: 216;float: left;margin: 0 8px 0 0;padding: 0 18px 15px;text-align: center;"><img src="category/0<%=(Integer.parseInt(id)-1) %>.jpg" width="130px" height="130px" /></li>
              <%
                      }
                  }
              %>
          </ul>複製代碼
          • 核心代碼2:清空瀏覽記錄
          /**
           * 清空瀏覽記錄
           */
          public class ClearServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  Cookie c = new Cookie("ids","");
          
                  //cookie的路徑與 CategoryServlet中的cookie中的路徑要相同
                  c.setPath(request.getContextPath()+"/");
                  //直接將cookie設置成無效
                  c.setMaxAge(0);
                  response.addCookie(c);
          
                  //重定向
                  response.sendRedirect(request.getContextPath()+"/category_list.jsp");
              }
          
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  doPost(request,response);
              }
          }複製代碼
          • 注意:
          1. cookie是不能跨瀏覽器的
          2. cookie不支持中文,須要編碼

          Session

          Session簡介

          Session是一種記錄客戶狀態的機制,不一樣於Cookie的是Cookie保存在客戶端瀏覽器中,而Session保存在服務器上。 客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session。 客戶端瀏覽器再次訪問時只須要從該Session中查找該客戶的狀態就能夠了。

          若是說Cookie機制是經過檢查客戶身上的"通行證"來肯定客戶身份的話, 那麼Session機制就是經過檢查服務器上的"客戶明細表"來確認客戶身份。 Session至關於程序在服務器上創建的一份客戶檔案, 客戶來訪的時候只須要查詢客戶檔案表就能夠了。

          Session機制

          一方面,咱們能夠把客戶端瀏覽器與服務器之間一系列交互的動做稱爲一個 Session。 從這個語義出發,咱們會提到Session持續的時間,會提到在Session過程當中進行了什麼操做等等。

          另外一方面,Session指的是服務器端爲客戶端所開闢的存儲空間,該空間保存的信息就是用於保持狀態。 從這個語義出發,咱們則會提到往Session中存放什麼內容,如何根據鍵值從Session中獲取匹配的內容等。

          • 要使用Session,固然是先要建立Session。那麼Session在什麼時候建立呢?
          1. Session在服務器端程序運行的過程當中建立的,不一樣語言實現的應用程序有不一樣建立Session的方法, 在Java中是經過調用HttpServletRequest的getSession方法(使用true做爲參數)建立的。 建立Session的同時,服務器會爲該Session生成惟一的session id, 這個session id在隨後的請求中會被用來從新得到已經建立的Session
          2. Session被建立以後,就能夠調用Session相關的方法往Session中增長內容了, 而這些內容只會保存在服務器中,發到客戶端的只有session id
          3. 當客戶端再次發送請求的時候,會將這個session id帶上, 服務器接受到請求以後就會依據session id找到相應的Session,從而再次使用Session。

          Session的生命週期

          Session保存在服務器端。爲了得到更高的存取速度,服務器通常把Session放在內存中。 每一個用戶都會有一個獨立的Session。 若是Session內容過於複雜,當大量客戶訪問服務器時可能會致使內存溢出。 所以,Session裏的信息應該儘可能精簡。

          Session在用戶第一次訪問服務器的時候自動建立。 須要注意只有訪問JSP、Servlet等程序時纔會建立Session, 只訪問HTML、IMAGE等靜態資源並不會建立Session。 若是還沒有生成Session,也可使用request.getSession(true)強制生成Session。

          Session生成後,只要用戶繼續訪問,服務器就會更新Session的最後訪問時間,並維護該Session。 用戶每訪問服務器一次,不管是否讀寫Session,服務器都認爲該用戶的Session"活躍(active)"了一次。

          Session的有效期

          因爲會有愈來愈多的用戶訪問服務器,所以Session也會愈來愈多。 爲防止內存溢出,服務器會把長時間內沒有活躍的Session從內存刪除。 這個時間就是Session的超時時間。若是超過了超時時間沒訪問過服務器,Session就自動失效了。

          Session的超時時間爲maxInactiveInterval屬性, 能夠經過對應的getMaxInactiveInterval()獲取,經過setMaxInactiveInterval(longinterval)修改。

          Session的超時時間也能夠在web.xml中修改。 另外,經過調用Session的invalidate()方法可使Session失效。

          Session案例 : 購物車

          獲取Session
          HttpSession getSession(); //request.getSession()複製代碼
          域對象
          xxxAttribute  //存放私有數據複製代碼
          域對象生命週期
          • 建立:第一次調用request.getSession()
          • 銷燬:
          1. 服務器非正常關閉;
          2. session超時;

          默認超時時間:30 min

          手動設置超時:setMaxInactiveInterval(int) (單位:秒)

          1. 手動設置;

          Session接口中的invalidate()方法

          public void invalidate()複製代碼
          • 核心代碼1:將商品添加到購物車
          public class CartServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  response.setContentType("text/html;charset=utf-8");
                  PrintWriter out=response.getWriter();
          
                  //1.獲取商品名稱
                  String name = request.getParameter("name");
          
                  //2.獲取購物車,實際上就是存入session的map
                  HashMap<String,Integer> map = (HashMap<String, Integer>) request.getSession().getAttribute("cart");
          
                  Integer num = null;
          
                  //3.判斷購物車是否爲空
                  if(map==null){
                      //3.1 購物車爲空,說明是第一次將商品放入購物車
                      //先建立購物車,
                      map = new HashMap<>();
                      request.getSession().setAttribute("cart",map);
                      num = 1;
                  }else{
                      //3.2 購物車不爲空,判斷該商品以前是否已經加入購物車
                      num = map.get(name);
                      if(num == null){
                          //num==null,說明該商品以前未加入購物車
                          num = 1;
                      }else{
                          num ++ ;
                      }
                  }
                  map.put(name,num);
          
                  //4.提示信息
                  out.print("<center>已經將<b>"+name+"</b>添加到購物車中<hr></center>");
                  out.print("<center><a href='"+request.getContextPath()+"/category_list.jsp'>繼續購物</a></center><br/>");
                  out.print("<center><a href='"+request.getContextPath()+"/cart.jsp'>查看購物車</a><center>");
              }
          
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  doPost(request,response);
              }
          }複製代碼
          • 顯示購物車中信息
          <body>
              <div class="container">
                  <a href="${pageContext.request.contextPath}/category_list.jsp">繼續購物</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                  <a href="${pageContext.request.contextPath}/clearCart">清空購物車</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                  <div class="row">
                      <div style="margin:0 auto; margin-top:10px;width:950px;">
                          <strong style="font-size:16px;margin:5px 0;">訂單詳情</strong>
                          <table class="table table-bordered">
                              <tbody>
                              <tr class="warning" align="center">
                                  <th>商品</th>
                                  <th>數量</th>
                              </tr>
                              <%
                                  HashMap<String,Integer> map = (HashMap<String,Integer>)request.getSession().getAttribute("cart");
                                  if(map==null){
                                      out.print("<tr><th colspan='2'>親,購物車空空,先去逛逛~~</th></tr>");
                                  }else{
                                      for(String name : map.keySet()){
                                          out.print("<tr class='active'>");
          
                                          out.print("<td width='30%'>");
                                          out.print(name);
                                          out.print("</td>");
                                          out.print("<td width='20%'>");
                                          out.print(map.get(name));
                                          out.print("</td>");
          
                                          out.print("</tr>");
                                      }
                                  }
                              %>
                              </tbody>
                          </table>
                      </div>
                  </div>
              </div>
          </body>複製代碼
          • 核心代碼2:清空購物車
          public class ClearCartServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  request.getSession().invalidate();
          
                  response.sendRedirect(request.getContextPath()+"/cart.jsp");
              }
          
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  doPost(request,response);
              }
          }複製代碼

          實現會話跟蹤的技術

            1. Cookie


            向客戶端發送Cookie :

            Cookie c =new Cookie("name","value"); //建立Cookie 
            c.setMaxAge(60*60*24); //設置最大時效,此處設置的最大時效爲一天
            response.addCookie(c); //把Cookie放入到HTTP響應中複製代碼

            從客戶端讀取Cookie :

            String name ="name"; 
            Cookie[]cookies =request.getCookies(); 
            if(cookies !=null){ 
               for(int i= 0;i<cookies.length;i++){ 
                    Cookie cookie =cookies[i]; 
                    if(name.equals(cookis.getName())) 
                    //something is here. 
                    //you can get the value 
                    cookie.getValue(); 
               }
            }複製代碼

            優勢: 數據能夠持久保存,不須要服務器資源,簡單,基於文本的Key-Value

            缺點: 大小受到限制,用戶能夠禁用Cookie功能,因爲保存在本地,有必定的安全風險。

              1. URL 重寫


              在URL中添加用戶會話的信息做爲請求的參數, 或者將惟一的會話ID添加到URL結尾以標識一個會話。

              優勢: 在Cookie被禁用的時候依然可使用

              缺點: 必須對網站的URL進行編碼,全部頁面必須動態生成,不能用預先記錄下來的URL進行訪問。

                1. 隱藏的表單域


                <input type="hidden" name ="session" value="..."/>複製代碼

                優勢: Cookie被禁時可使用

                缺點: 全部頁面必須是表單提交以後的結果。

                  1. session


                  當一個用戶第一次訪問某個網站時會自動建立 HttpSession,每一個用戶能夠訪問他本身的HttpSession

                  能夠經過HttpServletRequest對象的getSession方法得到HttpSession。 經過HttpSession的setAttribute方法能夠將一個值放在HttpSession中, 經過調用 HttpSession對象的getAttribute方法,同時傳入屬性名就能夠獲取保存在HttpSession中的對象。

                  與上面三種方式不一樣的是,HttpSession放在服務器的內存中,所以不要將過大的對象放在裏面。 即便目前的Servlet容器能夠在內存將滿時將 HttpSession 中的對象移到其餘存儲設備中,可是這樣勢必影響性能。 添加到 HttpSession 中的值能夠是任意Java對象,這個對象最好實現了 Serializable接口, 這樣Servlet容器在必要的時候能夠將其序列化到文件中,不然在序列化時就會出現異常。

                  Cookie和Session的的區別

                  1. HTTP協議是無狀態的協議,服務端須要記錄用戶的狀態,就須要用某種機制來識別具體的用戶,這個機制就是Session。 Session典型的應用場景就是購物車,當點擊下單按鈕時,因爲HTTP協議無狀態,因此並不知道是哪一個用戶操做的, 因此服務端要爲特定的用戶建立了特定的Session,用於標識這個用戶,而且跟蹤用戶,這樣才知道購物車裏面的商品狀況。 這個Session是保存在服務端的,有一個惟一標識。在服務端保存Session的方法不少,內存、數據庫、文件都有。 集羣的時候也要考慮Session的轉移,在大型的網站,通常會有專門的Session服務器集羣, 用來保存用戶會話,這個時候 Session 信息都是放在內存的,此外,一些緩存服務好比Memcached之類的來放 Session。
                  2. 服務端使用Cookie來識別特定的客戶。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。 實際上大多數的應用都是用 Cookie 來實現Session跟蹤的, 第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,須要在 Cookie 裏面記錄一個session id, 之後每次請求把這個 session id發送到服務器,這樣就可使用對應的Seesion了。 若是客戶端的瀏覽器禁用了 Cookie 怎麼辦? 通常這種狀況下,會使用一種叫作URL重寫的技術來進行會話跟蹤, 即每次HTTP交互,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的參數,服務端據此來識別用戶。
                  3. Cookie其實還能夠用在一些方便用戶的場景下, 設想你某次登錄過一個網站,下次登陸的時候不想再次輸入帳號了,怎麼辦? 這個信息能夠寫到Cookie裏面,訪問網站的時候, 網站頁面的腳本能夠讀取這個信息,就自動幫你把用戶名給填了, 可以方便一下用戶。這也是Cookie名稱的由來,給用戶的一點甜頭。

                  總結:

                  • Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在集羣、數據庫、文件中。
                  • Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。
                  相關文章
                  相關標籤/搜索