cookie和session的超詳解(配圖)

lm_y發表於2017-08-23

Cookie和Session詳解

會話(Session)跟蹤是Web程式中常用的技術,用來跟蹤使用者的整個會話。常用的會話跟蹤技術是Cookie與Session。Cookie通過在客戶端記錄資訊確定使用者身份Session通過在伺服器端記錄資訊確定使用者身份

本章將系統地講述Cookie與Session機制,並比較說明什麼時候不能用Cookie,什麼時候不能用Session。


1.1  Cookie機制

在程式中,會話跟蹤是很重要的事情。理論上,一個使用者的所有請求操作都應該屬於同一個會話,而另一個使用者的所有請求操作則應該屬於另一個會話,二者不能混淆。例如,使用者A在超市購買的任何商品都應該放在A的購物車內,不論是使用者A什麼時間購買的,這都是屬於同一個會話的,不能放入使用者B或使用者C的購物車內,這不屬於同一個會話。

而Web應用程式是使用HTTP協議傳輸資料的。HTTP協議是無狀態的協議。一旦資料交換完畢,客戶端與伺服器端的連線就會關閉,再次交換資料需要建立新的連線。這就意味著伺服器無法從連線上跟蹤會話。即使用者A購買了一件商品放入購物車內,當再次購買商品時伺服器已經無法判斷該購買行為是屬於使用者A的會話還是使用者B的會話了。要跟蹤該會話,必須引入一種機制。

Cookie就是這樣的一種機制。它可以彌補HTTP協議無狀態的不足。在Session出現之前,基本上所有的網站都採用Cookie來跟蹤會話。

1.1.1  什麼是Cookie

Cookie意為“甜餅”,是由W3C組織提出,最早由Netscape社群發展的一種機制。目前Cookie已經成為標準,所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支援Cookie。

由於HTTP是一種無狀態的協議,伺服器單從網路連線上無從知道客戶身份。怎麼辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣伺服器就能從通行證上確認客戶身份了。這就是Cookie的工作原理

Cookie實際上是一小段的文字資訊。客戶端請求伺服器,如果伺服器需要記錄該使用者狀態,就使用response向客戶端瀏覽器頒發一個Cookie。客戶端瀏覽器會把Cookie儲存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給伺服器。伺服器檢查該Cookie,以此來辨認使用者狀態。伺服器還可以根據需要修改Cookie的內容。



檢視某個網站頒發的Cookie很簡單。在瀏覽器位址列輸入javascript:alert (document. cookie)就可以了(需要有網才能檢視)。JavaScript指令碼會彈出一個對話方塊顯示本網站頒發的所有Cookie的內容,如圖1.1所示。


圖1.1  Baidu網站頒發的Cookie


圖1.1中彈出的對話方塊中顯示的為Baidu網站的Cookie。其中第一行BAIDUID記錄的就是筆者的身份helloweenvsfei,只是Baidu使用特殊的方法將Cookie資訊加密了。


注意:Cookie功能需要瀏覽器的支援。

如果瀏覽器不支援Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效。

不同的瀏覽器採用不同的方式儲存Cookie。

IE瀏覽器會在“C:\Documents and Settings\你的使用者名稱\Cookies”資料夾下以文字檔案形式儲存,一個文字檔案儲存一個Cookie。


1.1.2  記錄使用者訪問次數

Java中把Cookie封裝成了javax.servlet.http.Cookie類。每個Cookie都是該Cookie類的物件。伺服器通過操作Cookie類物件對客戶端Cookie進行操作。通過request.getCookie()獲取客戶端提交的所有Cookie(以Cookie[]陣列形式返回),通過response.addCookie(Cookiecookie)向客戶端設定Cookie。

Cookie物件使用key-value屬性對的形式儲存使用者狀態,一個Cookie物件儲存一個屬性對,一個request或者response同時使用多個Cookie。因為Cookie類位於包javax.servlet.http.*下面,所以JSP中不需要import該類。


1.1.3  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做類似的處理。


1.1.4  Unicode編碼:儲存中文

中文與英文字元不同,中文屬於Unicode字元,在記憶體中佔4個字元,而英文屬於ASCII字元,記憶體中只佔2個位元組。Cookie中使用Unicode字元時需要對Unicode字元進行編碼,否則會亂碼。


提示:Cookie中儲存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因為瀏覽器不一定支援,而且JavaScript也不支援GBK編碼。


1.1.5  BASE64編碼:儲存二進位制圖片

Cookie不僅可以使用ASCII字元與Unicode字元,還可以使用二進位制資料。例如在Cookie中使用數字證照,提供安全度。使用二進位制資料時也需要進行編碼。

%注意:本程式僅用於展示Cookie中可以儲存二進位制內容,並不實用。由於瀏覽器每次請求伺服器都會攜帶Cookie,因此Cookie內容不宜過多,否則影響速度。Cookie的內容應該少而精。


1.1.6  設定Cookie的所有屬性

除了name與value之外,Cookie還具有其他幾個常用的屬性。每個屬性對應一個getter方法與一個setter方法。Cookie類的所有屬性如表1.1所示。

表1.1  Cookie常用屬性

屬  性  名

描    述

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規範


1.1.7  Cookie的有效期

Cookie的maxAge決定著Cookie的有效期,單位為秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。

如果maxAge屬性為正數,則表示該Cookie會在maxAge秒之後自動失效。瀏覽器會將maxAge為正數的Cookie持久化,即寫到對應的Cookie檔案中。無論客戶關閉了瀏覽器還是電腦,只要還在maxAge秒之前,登入網站時該Cookie仍然有效。下面程式碼中的Cookie資訊將永遠有效。


Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(Integer.MAX_VALUE);           // 設定生命週期為MAX_VALUE

response.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","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(0);                          // 設定生命週期為0,不能為負數

response.addCookie(cookie);                    // 必須執行這一句


response物件提供的Cookie操作方法只有一個新增操作add(Cookie cookie)。

要想修改Cookie只能使用一個同名的Cookie來覆蓋原來的Cookie,達到修改的目的。刪除時只需要把maxAge修改為0即可。


注意:從客戶端讀取Cookie時,包括maxAge在內的其他屬性都是不可讀的,也不會被提交。瀏覽器提交Cookie時只會提交name與value屬性。maxAge屬性只被瀏覽器用來判斷Cookie是否過期。


1.1.8  Cookie的修改、刪除

Cookie並不提供修改、刪除操作。如果要修改某個Cookie,只需要新建一個同名的Cookie,新增到response中覆蓋原來的Cookie。

如果要刪除某個Cookie,只需要新建一個同名的Cookie,並將maxAge設定為0,並新增到response中覆蓋原來的Cookie。注意是0而不是負數。負數代表其他的意義。讀者可以通過上例的程式進行驗證,設定不同的屬性。


注意:修改、刪除Cookie時,新建的Cookie除value、maxAge之外的所有屬性,例如name、path、domain等,都要與原Cookie完全一樣。否則,瀏覽器將視為兩個不同的Cookie不予覆蓋,導致修改、刪除失敗。


1.1.9  Cookie的域名

Cookie是不可跨域名的。域名www.google.com頒發的Cookie不會被提交到域名www.baidu.com去。這是由Cookie的隱私安全機制決定的。隱私安全機制能夠禁止網站非法獲取其他網站的Cookie。

正常情況下,同一個一級域名下的兩個二級域名如www.helloweenvsfei.com和images.helloweenvsfei.com也不能互動使用Cookie,因為二者的域名並不嚴格相同。如果想所有helloweenvsfei.com名下的二級域名都可以使用該Cookie,需要設定Cookie的domain引數,例如:

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

cookie.setDomain(".helloweenvsfei.com");           // 設定域名

cookie.setPath("/");                              // 設定路徑

cookie.setMaxAge(Integer.MAX_VALUE);               // 設定有效期

response.addCookie(cookie);                       // 輸出到客戶端


讀者可以修改本機C:\WINDOWS\system32\drivers\etc下的hosts檔案來配置多個臨時域名,然後使用setCookie.jsp程式來設定跨域名Cookie驗證domain屬性。

注意:domain引數必須以點(".")開始。另外,name相同但domain不同的兩個Cookie是兩個不同的Cookie。如果想要兩個域名完全不同的網站共有Cookie,可以生成兩個Cookie,domain屬性分別為兩個域名,輸出到客戶端。


1.1.10  Cookie的路徑

domain屬性決定執行訪問Cookie的域名,而path屬性決定允許訪問Cookie的路徑(ContextPath)。例如,如果只允許/sessionWeb/下的程式使用Cookie,可以這麼寫:

Cookie cookie = new Cookie("time","20080808");     // 新建Cookie

cookie.setPath("/session/");                          // 設定路徑

response.addCookie(cookie);                           // 輸出到客戶端

設定為“/”時允許所有路徑使用Cookie。path屬性需要使用符號“/”結尾。name相同但domain相同的兩個Cookie也是兩個不同的Cookie。


注意:頁面只能獲取它屬於的Path的Cookie。例如/session/test/a.jsp不能獲取到路徑為/session/abc/的Cookie。使用時一定要注意。


1.1.11  Cookie的安全屬性

HTTP協議不僅是無狀態的,而且是不安全的。使用HTTP協議的資料不經過任何加密就直接在網路上傳播,有被截獲的可能。使用HTTP協議傳輸很機密的內容是一種隱患。如果不希望Cookie在HTTP等非安全協議中傳輸,可以設定Cookie的secure屬性為true。瀏覽器只會在HTTPS和SSL等安全協議中傳輸此類Cookie。下面的程式碼設定secure屬性為true:


Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie

cookie.setSecure(true);                           // 設定安全屬性

response.addCookie(cookie);                        // 輸出到客戶端


提示:secure屬性並不能對Cookie內容加密,因而不能保證絕對的安全性。如果需要高安全性,需要在程式中對Cookie內容加密、解密,以防洩密。


1.1.12  JavaScript操作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不會有任何結果。


1.1.13  案例:永久登入

如果使用者是在自己家的電腦上上網,登入時就可以記住他的登入資訊,下次訪問時不需要再次登入,直接訪問即可。實現方法是把登入資訊如賬號、密碼等儲存在Cookie中,並控制Cookie的有效期,下次訪問時再驗證Cookie中的登入資訊即可。

儲存登入資訊有多種方案。最直接的是把使用者名稱與密碼都保持到Cookie中,下次訪問時檢查Cookie中的使用者名稱與密碼,與資料庫比較。這是一種比較危險的選擇,一般不把密碼等重要資訊儲存到Cookie中

還有一種方案是把密碼加密後儲存到Cookie中,下次訪問時解密並與資料庫比較。這種方案略微安全一些。如果不希望儲存密碼,還可以把登入的時間戳儲存到Cookie與資料庫中,到時只驗證使用者名稱與登入時間戳就可以了。

這幾種方案驗證賬號時都要查詢資料庫。

本例將採用另一種方案,只在登入時查詢一次資料庫,以後訪問驗證登入資訊時不再查詢資料庫。實現方式是把賬號按照一定的規則加密後,連同賬號一塊儲存到Cookie中。下次訪問時只需要判斷賬號的加密規則是否正確即可。本例把賬號儲存到名為account的Cookie中,把賬號連同金鑰用MD1演算法加密後儲存到名為ssid的Cookie中。驗證時驗證Cookie中的賬號與金鑰加密後是否與Cookie中的ssid相等。相關程式碼如下:

程式碼1.8 loginCookie.jsp

<%@ page language="java"pageEncoding="UTF-8" isErrorPage="false" %>

<%!                                                  // JSP方法

    private static final String KEY =":cookie@helloweenvsfei.com";
                                                     // 金鑰 

    public final static String calcMD1(Stringss) { // MD1 加密演算法

       String s = ss==null ?"" : ss;                  // 若為null返回空

       char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9',
       'a', 'b', 'c', 'd', 'e', 'f' };                        // 字典

       try {

        byte[] strTemp =s.getBytes();                          // 獲取位元組

        MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 獲取MD1

       mdTemp.update(strTemp);                                // 更新資料

        byte[] md =mdTemp.digest();                        // 加密

        int j =md.length;                                 // 加密後的長度

        char str[] = newchar[j * 2];                       // 新字串陣列

        int k =0;                                         // 計數器k

        for (int i = 0; i< j; i++) {                       // 迴圈輸出

         byte byte0 =md[i];

         str[k++] =hexDigits[byte0 >>> 4 & 0xf];

         str[k++] =hexDigits[byte0 & 0xf];

        }

        return newString(str);                             // 加密後字串

       } catch (Exception e){return null; }

    }

%>

<%

   request.setCharacterEncoding("UTF-8");             // 設定request編碼

    response.setCharacterEncoding("UTF-8");        // 設定response編碼

   

    String action =request.getParameter("action"); // 獲取action引數

   

    if("login".equals(action)){                       // 如果為login動作

        String account =request.getParameter("account");
                                                     // 獲取account引數

        String password =request.getParameter("password");
                                                     // 獲取password引數

        int timeout = newInteger(request.getParameter("timeout"));
                                                     // 獲取timeout引數

              

        String ssid =calcMD1(account + KEY); // 把賬號、金鑰使用MD1加密後儲存

       

        CookieaccountCookie = new Cookie("account", account);
                                                     // 新建Cookie

       accountCookie.setMaxAge(timeout);              // 設定有效期

       

        Cookie ssidCookie =new Cookie("ssid", ssid);   // 新建Cookie

       ssidCookie.setMaxAge(timeout);                 // 設定有效期

       

       response.addCookie(accountCookie);             // 輸出到客戶端

       response.addCookie(ssidCookie);            // 輸出到客戶端

       

        // 重新請求本頁面,引數中帶有時間戳,禁止瀏覽器快取頁面內容

       response.sendRedirect(request.getRequestURI() + "?" + System.
        currentTimeMillis());

        return;

    }

    elseif("logout".equals(action)){                  // 如果為logout動作

       

        CookieaccountCookie = new Cookie("account", "");
                                                 // 新建Cookie,內容為空

       accountCookie.setMaxAge(0);                // 設定有效期為0,刪除

              

        Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,內容為空

       ssidCookie.setMaxAge(0);                   // 設定有效期為0,刪除

       response.addCookie(accountCookie);         // 輸出到客戶端

       response.addCookie(ssidCookie);         // 輸出到客戶端

        //重新請求本頁面,引數中帶有時間戳,禁止瀏覽器快取頁面內容

       response.sendRedirect(request.getRequestURI() + "?" + System.
        currentTimeMillis());

        return;

    }

    boolean login = false;                        // 是否登入

    String account = null;                        // 賬號

    String ssid = null;                           // SSID標識

   

    if(request.getCookies() !=null){               // 如果Cookie不為空

        for(Cookie cookie :request.getCookies()){  // 遍歷Cookie

           if(cookie.getName().equals("account"))  // 如果Cookie名為
                                                    account

               account = cookie.getValue();       // 儲存account內容

           if(cookie.getName().equals("ssid")) // 如果為SSID

               ssid = cookie.getValue();          // 儲存SSID內容

        }

    }

    if(account != null && ssid !=null){    // 如果account、SSID都不為空

        login =ssid.equals(calcMD1(account + KEY));
                                      // 如果加密規則正確, 則視為已經登入

    }

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">

       <legend><%= login ? "歡迎您回來" : "請先登入"%></legend>

        <% if(login){%>

            歡迎您, ${ cookie.account.value }. &nbsp;&nbsp;

           <a href="${ pageContext.request.requestURI }?action=logout">
            登出</a>

        <% } else {%>

        <formaction="${ pageContext.request.requestURI }?action=login"
        method="post">

           <table>

               <tr><td>賬號: </td>

                   <td><input type="text"name="account" style="width:
                   200px; "></td>

               </tr>

               <tr><td>密碼: </td>

                   <td><inputtype="password" name="password"></td>

               </tr>

               <tr>

                   <td>有效期: </td>

                   <td><inputtype="radio" name="timeout" value="-1"
                   checked> 關閉瀏覽器即失效 <br/> <input type="radio"
                   name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天
                   內有效 <br/><input type="radio" name="timeout" value=
                   "<%= Integer.MAX_VALUE %>"> 永久有效 <br/> </td> </tr>

               <tr><td></td>

                   <td><input type="submit"value=" 登  錄 " class=
                   "button"></td>

               </tr>

           </table>

        </form>

        <% } %>

登入時可以選擇登入資訊的有效期:關閉瀏覽器即失效、30天內有效與永久有效。通過設定Cookie的age屬性來實現,注意觀察程式碼。執行效果如圖1.7所示。


圖1.7  永久登入

提示:該加密機制中最重要的部分為演算法與金鑰。由於MD1演算法的不可逆性,即使使用者知道了賬號與加密後的字串,也不可能解密得到金鑰。因此,只要保管好金鑰與演算法,該機制就是安全的。


1.2  Session機制

除了使用Cookie,Web應用程式中還經常使用Session來記錄客戶端狀態。Session是伺服器端使用的一種記錄客戶端狀態的機制,使用上比Cookie簡單一些,相應的也增加了伺服器的儲存壓力

1.2.1  什麼是Session

Session是另一種記錄客戶狀態的機制,不同的是Cookie儲存在客戶端瀏覽器中,而Session儲存在伺服器上。客戶端瀏覽器訪問伺服器的時候,伺服器把客戶端資訊以某種形式記錄在伺服器上。這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查詢該客戶的狀態就可以了。

如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那麼Session機制就是通過檢查伺服器上的“客戶明細表”來確認客戶身份。Session相當於程式在伺服器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。

1.2.2  實現使用者登入

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中的屬性

   

out.println("登入時間為:" +(Date)session.getAttribute("loginTime"));      // 獲取Session屬性

request還可以使用getSession(boolean create)來獲取Session。區別是如果該客戶的Session不存在,request.getSession()方法會返回null,而getSession(true)會先建立Session再將Session返回。

Servlet中必須使用request來程式設計式獲取HttpSession物件,而JSP中內建了Session隱藏物件,可以直接使用。如果使用宣告瞭<%@page session="false" %>,則Session隱藏物件不可用。下面的例子使用Session記錄客戶賬號資訊。

原始碼如下:

程式碼1.9  session.jsp

<%@ page language="java" pageEncoding="UTF-8"%>

<jsp:directive.page import="com.helloweenvsfei.sessionWeb.bean.Person"/>

<jsp:directive.page import="java.text.SimpleDateFormat"/>

<jsp:directive.page import="java.text.DateFormat"/>

<jsp:directive.page import="java.util.Date"/>

<%!

    DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器

%>

<%

    response.setCharacterEncoding("UTF-8");        // 設定request編碼

    Person[] persons =

    {          

       // 基礎資料,儲存三個人的資訊

        new Person("Liu Jinghua","password1", 34, dateFormat.parse
        ("1982-01-01")),

        new Person("Hello Kitty","hellokitty", 23, dateFormat.parse
        ("1984-02-21")),

        new Person("Garfield", "garfield_pass",23, dateFormat.parse
        ("1994-09-12")),

     };

   

    String message = "";                      // 要顯示的訊息

   

    if(request.getMethod().equals("POST"))

    {

        // 如果是POST登入       

        for(Person person :persons)

        {          

            // 遍歷基礎資料,驗證賬號、密碼

            // 如果使用者名稱正確且密碼正確

           if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password")))

           {              

               // 登入成功,設定將使用者的資訊以及登入時間儲存到Session

               session.setAttribute("person", person);                   // 儲存登入的Person

               session.setAttribute("loginTime", new Date());          // 儲存登入的時間              

               response.sendRedirect(request.getContextPath() + "/welcome.jsp");

               return;

            }

        }      

        message = "使用者名稱密碼不匹配,登入失敗。";       // 登入失敗

    }

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">

<html>

    // ... HTML程式碼為一個FORM表單,程式碼略,請看隨書光碟

</html>


登入介面驗證使用者登入資訊,如果登入正確,就把使用者資訊以及登入時間儲存進Session,然後轉到歡迎頁面welcome.jsp。welcome.jsp中從Session中獲取資訊,並將使用者資料顯示出來。

welcome.jsp程式碼如下:

程式碼1.10  welcome.jsp

<%@ page language="java" pageEncoding="UTF-8"%>

<jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/>

<jsp:directive.page import="java.text.SimpleDateFormat"/>

<jsp:directive.page import="java.text.DateFormat"/>

<jsp:directive.page import="java.util.Date"/>

<%!

    DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器

%>

<%

    Person person =(Person)session.getAttribute("person");                       // 獲取登入的person

    Date loginTime =(Date)session.getAttribute("loginTime");                     // 獲取登入時間

%>

    // ... 部分HTML程式碼略

            <table>

               <tr><td>您的姓名:</td>

                   <td><%= person.getName()%></td>

               </tr>

               <tr><td>登入時間:</td>

                   <td><%= loginTime%></td>

               </tr>

               <tr><td>您的年齡:</td>

                   <td><%= person.getAge()%></td>

               </tr>

               <tr><td>您的生日:</td>

                   <td><%=dateFormat.format(person.getBirthday()) %></td>

               </tr>

            </table>

程式執行效果如圖1.8所示。


圖1.8  使用Session記錄使用者資訊

注意程式中Session中直接儲存了Person類物件與Date類物件,使用起來要比Cookie方便。

當多個客戶端執行程式時,伺服器會儲存多個客戶端的Session。獲取Session的時候也不需要宣告獲取誰的Session。Session機制決定了當前客戶只會獲取到自己的Session,而不會獲取到別人的Session。各客戶的Session也彼此獨立,互不可見


提示Session的使用比Cookie方便,但是過多的Session儲存在伺服器記憶體中,會對伺服器造成壓力。


1.2.3  Session的生命週期

Session儲存在伺服器端。為了獲得更高的存取速度,伺服器一般把Session放在記憶體裡。每個使用者都會有一個獨立的Session。如果Session內容過於複雜,當大量客戶訪問伺服器時可能會導致記憶體溢位。因此,Session裡的資訊應該儘量精簡。

Session在使用者第一次訪問伺服器的時候自動建立。需要注意只有訪問JSP、Servlet等程式時才會建立Session,只訪問HTML、IMAGE等靜態資源並不會建立Session。如果尚未生成Session,也可以使用request.getSession(true)強制生成Session。

Session生成後,只要使用者繼續訪問,伺服器就會更新Session的最後訪問時間,並維護該Session。使用者每訪問伺服器一次,無論是否讀寫Session,伺服器都認為該使用者的Session“活躍(active)”了一次。


1.2.4  Session的有效期

由於會有越來越多的使用者訪問伺服器,因此Session也會越來越多。為防止記憶體溢位,伺服器會把長時間內沒有活躍的Session從記憶體刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過伺服器,Session就自動失效了。

Session的超時時間為maxInactiveInterval屬性,可以通過對應的getMaxInactiveInterval()獲取,通過setMaxInactiveInterval(longinterval)修改。

Session的超時時間也可以在web.xml中修改。另外,通過呼叫Session的invalidate()方法可以使Session失效。


1.2.5  Session的常用方法

Session中包括各種方法,使用起來要比Cookie方便得多。Session的常用方法如表1.2所示。

表1.2  HttpSession的常用方法

方  法  名

描    述

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)修改超時時間。可以修改web.xml改變Session的預設超時時間。例如修改為60分鐘:

<session-config>

   <session-timeout>60</session-timeout>      <!-- 單位:分鐘 -->

</session-config>


注意:<session-timeout>引數的單位為分鐘,而setMaxInactiveInterval(int s)單位為秒。


1.2.6  Session對瀏覽器的要求

雖然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地址重寫。


1.2.7  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=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引數的前面新增了字串“;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了。


1.2.8  Session中禁止使用Cookie

既然WAP上大部分的客戶瀏覽器都不支援Cookie,索性禁止Session使用Cookie,統一使用URL地址重寫會更好一些。Java Web規範支援通過配置的方式禁用Cookie。下面舉例說一下怎樣通過配置禁止使用Cookie。

開啟專案sessionWeb的WebRoot目錄下的META-INF資料夾(跟WEB-INF資料夾同級,如果沒有則建立),開啟context.xml(如果沒有則建立),編輯內容如下:

程式碼1.11 /META-INF/context.xml

<?xml version='1.0' encoding='UTF-8'?>

<Context path="/sessionWeb"cookies="false">

</Context>


或者修改Tomcat全域性的conf/context.xml,修改內容如下:

程式碼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讀寫。也就是說伺服器不會自動維護名為JSESSIONID的Cookie了,但是程式中仍然可以讀寫其他的Cookie。

相關文章