Cookie講解(1)

Cookie經過在客戶端記錄信息肯定用戶身份,Session經過在服務器端記錄信息肯定用戶身份。 
Web應用程序是使用HTTP協議傳輸數據的。HTTP協議是無狀態的協議。一旦數據交換完畢,客戶端與服務器端的鏈接就會關閉,再次交換數據須要創建新的鏈接。這就意味着服務器沒法從鏈接上跟蹤會話。 
即用戶A購買了一件商品放入購物車內,當再次購買商品時服務器已經沒法判斷該購買行爲是屬於用戶A的會話仍是用戶B的會話了。要跟蹤該會話,必須引入一種機制。 
Cookie就是這樣的一種機制。它能夠彌補HTTP協議無狀態的不足。在Session出現以前,基本上全部的網站都採用Cookie來跟蹤會話。html

1.什麼是Cookie

Cookie其實是一小段的文本信息。客戶端請求服務器,若是服務器須要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie。 客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,以 此來辨認用戶狀態。服務器還能夠根據須要修改Cookie的內容。 
Java中把Cookie封裝成了javax.servlet.http.Cookie類。每一個Cookie都是該Cookie類的對象。服務器經過操做Cookie類對象對客戶端Cookie進行操做。經過request.getCookie()獲取客戶端提交的全部Cookie(以Cookie[]數組形式返回),經過response.addCookie(Cookiecookie)向客戶端設置Cookie。java

2.Cookie 對象的內容

Cookie對象使用key-value屬性對的形式保存用戶狀態,一個Cookie對象保存一個屬性對,一個request或者response同時使用多個Cookie。由於Cookie類位於包javax.servlet.http.*下面,因此JSP中不須要import該類。git

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。web

中文與英文字符不一樣,中文屬於Unicode字符,在內存中佔4個字符,而英文屬於ASCII字符,內存中只佔2個字節。Cookie中使用Unicode字符時須要對Unicode字符進行編碼,不然會亂碼。 
提示:Cookie中保存中文只能編碼。通常使用UTF-8編碼便可。不推薦使用GBK等中文編碼,由於瀏覽器不必定支持,並且JavaScript也不支持GBK編碼.算法

4.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是否過時。

5.Cookie的修改、刪除

Cookie並不提供修改、刪除操做。若是要修改某個Cookie,只須要新建一個同名的Cookie,添加到response中覆蓋原來的Cookie。 
若是要刪除某個Cookie,只須要新建一個同名的Cookie,並將maxAge設置爲0,並添加到response中覆蓋原來的Cookie。注意是0而不是負數。負數表明其餘的意義。讀者能夠經過上例的程序進行驗證,設置不一樣的屬性。

注意:修改、刪除Cookie時,新建的Cookie除value、maxAge以外的全部屬性,例如name、path、domain等,都要與原Cookie徹底同樣。不然,瀏覽器將視爲兩個不一樣的Cookie不予覆蓋,致使修改、刪除失敗。

6.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屬性分別爲兩個域名,輸出到客戶端。 
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。使用時必定要注意。

7.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內容加密、解密,以防泄密。

8.JavaScript操做Cookie

Cookie是保存在瀏覽器端的,所以瀏覽器具備操做Cookie的先決條件。瀏覽器可使用腳本程序如 JavaScript等操做Cookie。這裏以JavaScript爲例介紹經常使用的Cookie操做。例以下面的代碼會輸出本頁面 全部的Cookie。

<script>document.write(document.cookie);</script>

因爲JavaScript可以任意地讀寫Cookie,有些好事者便想使用JavaScript程序去窺探用戶在其餘網 站的Cookie。不過這是徒勞的,W3C組織早就意識到JavaScript對Cookie的讀寫所帶來的安全隱患並加以防備了,W3C標準的瀏覽器會 阻止JavaScript讀寫任何不屬於本身網站的Cookie。換句話說,A網站的JavaScript程序讀寫B網站的Cookie不會有任何結果。

9.永久登陸

若是用戶是在本身家的電腦上上網,登陸時就能夠記住他的登陸信息,下次訪問時不須要再次登陸,直接訪問便可。實現方法是把登陸信息如帳號、密碼等保存在Cookie中,並控制Cookie的有效期,下次訪問時再驗證Cookie中的登陸信息便可。 
保存登陸信息有多種方案。最直接的是把用戶名與密碼都保持到Cookie中,下次訪問時檢查Cookie中的用戶名與密碼,與數據庫比較。這是一種比較危險的選擇,通常不把密碼等重要信息保存到Cookie中。 
還有一種方案是把密碼加密後保存到Cookie中,下次訪問時解密並與數據庫比較。這種方案略微安全一些。若是不但願保存密碼,還能夠把登陸的時間戳保存到Cookie與數據庫中,到時只驗證用戶名與登陸時間戳就能夠了。 
這幾種方案驗證帳號時都要查詢數據庫。

咱們實現一個永久登陸的Demo: 
方法:只在登陸時查詢一次數據庫,之後訪問驗證登陸信息時再也不查詢數據庫。實現方式是把帳號按照必定的規則加密後,連同帳號一塊保存到Cookie中。下次訪問時只須要判斷帳號的加密規則是否正確便可。本例把帳號保存到名爲account的Cookie中,把帳號連同密鑰用MD1算法加密後保存到名爲ssid的Cookie中。驗證時驗證Cookie中的帳號與密鑰加密後是否與Cookie中的ssid相等。相關代碼以下:

loginCookie.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@page import="java.security.MessageDigest"%>
<%!                                                  // JSP方法
    private static final String KEY =":cookie@wangxi.com";
                                                     // 密鑰 
    public final static String calcMD1(String ss) { // 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();                          // 獲取字節
        MessageDigest mdTemp = MessageDigest.getInstance("MD1"); // 獲取MD1
        mdTemp.update(strTemp);                                // 更新數據
        byte[] md =mdTemp.digest();                        // 加密
        int j =md.length;                                 // 加密後的長度
        char str[] = new char[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 new String(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 = new Integer(request.getParameter("timeout"));
                                                     // 獲取timeout參數

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

        Cookie accountCookie = 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;
    }
    else if("logout".equals(action)){              // 若是爲logout動做

        Cookie accountCookie = 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 = 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>
                   <input type="password" name="password">
                   </td>
               </tr>
               <tr>
                   <td>有效期: </td>
                   <td>
                   <input type="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>
        <% } %>

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>

即在文件名的後面,在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了。

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。

相關文章
相關標籤/搜索