什麼是 Cookie?
Cookie 是一小段文本資訊,伴隨著使用者請求和頁面在 Web 服務器和瀏覽器之間傳遞。使用者每次訪問網站時,Web 應用程式都可以讀取 Cookie 包含的資訊。Cookie 最根本的用途是 Cookie 能夠協助 Web 網站儲存有關訪問者的資訊。更概括地說,Cookie 是一種保持 Web 應用程式連續性(即執行“狀態管理”)的方法。
我參考了 xxol.net 上一篇關於Cookies的連載,花了近一周的時間把這篇發章發完了,並提供了C#版本源碼。原文是用VB.net開發的,雖然聽說是出自Microsoft的教程,不過仍有幾段程式不能直接移植(可能是寫教程時 .NET Framework 版本較低)。不過我承諾,我所提供的C#程式在 .NET Framework 1.1 中是完全能調試通過的。
如果您對我的這篇文字比較感興趣,我願意提供 Creative Commons License 前提下的文字轉載授權。當然,如果您對本文有任何問題也歡迎您來信(mailto: ryun dot cn (a) gmail dot com)或在本文下方的評論中給我留言。
Article Summary (Created by Macromedia FlashPager 2):
Open Link
Source Codes (Created by Visual Studio .net 2003):
Download Codes Password:
ryun.cn
Cookie 的限制
大多數瀏覽器支援每個網站儲存 20 個最多可達 4096 位元組的 Cookie,如果試圖儲存更多的 Cookie,則最先儲存的 Cookie 就會被刪除。最可能遇到的 Cookie 限制是:使用者可以設定自己的瀏覽器,拒絕接受 Cookie。儘管 Cookie 在應用程式中非常有用,應用程式也不應該依賴於能夠儲存 Cookie。利用 Cookie 可以做到錦上添花,但不要利用它們來支援關鍵功能。
編寫 Cookie
您可以利用頁面的 Response 屬性來編寫 Cookie,該屬性提供的對象使使用者可以將資訊添加到由頁面向瀏覽器呈現的資訊中。Response 對象支援一個名為 Cookies 的集合,您可以向其中添加要寫入瀏覽器的 Cookie。可以通過多種方法把 Cookie 添加到 Response.Cookies 集合中:
Response.Cookies["userName"].Value="Ryun";
Response.Cookies["userName"].Expires=DateTime.Now.AddDays(1);
HttpCookie aCookie=new HttpCookie("lastVisit");
aCookie.Value=DateTime.Now.ToString();
aCookie.Expires=DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);
第一個 Cookie 直接設定了 Response.Cookies 集合的值。您可以使用這種方法向集合中添加值,因為 Response.Cookies 是從 NameObjectCollectionBase 類型的特殊集合派生得到的。第二個則建立了 Cookie 對象的一個執行個體(HttpCookie 類型),並設定了其屬性,然後通過 Add 方法把它添加到 Response.Cookies 集合。執行個體化 HttpCookie 對象時,您必須把 Cookie 名稱作為建構函式的一部分進行傳遞。
查看 Cookie
查看 Cookie 是比較容易的,以 Internet Explorer 為例:“工具”»“Internet 選項”»“常規”»“設定”»“查看檔案”» 以“Cookie:”開頭的就是 Cookie 檔案。
多值 Cookie(子鍵)
您也可以在一個 Cookie 中儲存多個成對的名稱和數值。成對的名稱和數值也稱作“鍵”或“子鍵”,例如,如果不希望建立名為“userName”和“lastVisit”的兩個單獨的 Cookie,可以建立一個名為“userInfo”的 Cookie,並使其包含兩個子鍵:“userName”和“lastVisit”。
以下樣本顯示了編寫同一 Cookie 的兩種不同方法,其中的每個 Cookie 都帶有兩個子鍵:
Response.Cookies["userInfo"]["userName"]="Ryun";
Response.Cookies["userInfo"]["lastVisit"]=DateTime.Now.ToString();
Response.Cookies["userInfo"].Expires=DateTime.Now.AddDays(1);
HttpCookie aCookie=new HttpCookie("userInfo");
aCookie.Values["userName"]="Ryun";
aCookie.Values["lastVisit"]=DateTime.Now.ToString();
aCookie.Expires=DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);
控制 Cookie 有效範圍
您可以通過兩種方法設定 Cookie 的有效範圍,一種是把 Cookie 的有效範圍限制在伺服器上的一個檔案夾中,實際上這樣就將 Cookie 限制到網站上的某個應用程式,另一種是把有效範圍設定為某個域,從而允許您指定域中的哪些子域可以訪問。
HttpCookie appCookie=new HttpCookie("AppCookie");
appCookie.Value="Written " + DateTime.Now.ToString();
appCookie.Expires=DateTime.Now.AddDays(1); appCookie.Path="/Ruly";
Response.Cookies.Add(appCookie);
將 Cookie 限制到伺服器上的某個檔案夾,用以上方法就是將 Cookie 的 Path 屬性限制在“/Ruly”檔案夾內)。提示:通過對 Internet Explorer 和 Mozilla 瀏覽器進行測試發現,此處使用的路徑是區分大小寫。
如果按照下面的方式設定域,則 Cookie 能用於指定子域中的頁面。(只在“http://ruly.ryun.cn/”內有效)
Response.Cookies["subCookie"].Value=DateTime.Now.ToString();
Response.Cookies["subCookie"].Expires=DateTime.Now.AddDays(1);
Response.Cookies["subCookie"].Domain="ruly.ryun.cn";
你用如下方法就可以把 Cookie 用於主域(ryun.cn)、和子域(ruly.ryun.cn)中:
Response.Cookies["subCookie"].Value=DateTime.Now.ToString();
Response.Cookies["subCookie"].Expires=DateTime.Now.AddDays(1);
Response.Cookies["subCookie"].Domain="ryun.cn";
讀取 Cookie
當瀏覽器向伺服器發送請求時,該伺服器的 Cookie 會與請求一起發送。在 ASP.net 中,您可以使用 Request 對象來讀取 Cookie。以下樣本顯示了兩種方法,目的都是擷取名為“username”的 Cookie 的值並將值顯示在 Label 控制項中:
string strOutput="";
if (Request.Cookies["userName"] != null)
{
strOutput = Server.HtmlEncode(Request.Cookies["userName"].Value);
}
if (Request.Cookies["lastVisit"] != null)
{
HttpCookie aCookie = Request.Cookies["lastVisit"];
labInfo.Text = strOutput + " " + Server.HtmlEncode(aCookie.Value);
}
在擷取 Cookie 的值之前,應該確保該 Cookie 確實存在。否則,您將得到一個 System.NullReferenceException 異常。注意:在頁面中顯示 Cookie 的內容之前,最好調用 HttpServerUtility.HtmlEncode 方法對 Cookie 的內容進行編碼。之所以這樣做,是因為我要顯示 Cookie 的內容,而且要確保 Cookie 中沒有任何惡意可執行指令碼。另外,同一台電腦上的不同瀏覽器不一定能夠相互讀取各自的 Cookie。
以下是擷取子索引值的一種方法:
string strOutput="";
if (Request.Cookies["userInfo"] != null)
{
strOutput = Server.HtmlEncode(Request.Cookies["userInfo"]["userName"]);
labInfo.Text = strOutput + " " + Server.HtmlEncode(Request.Cookies["userInfo"]["lastVisit"]);
}
Cookie 是用字串的形式儲存值的,如要將 lastVisit 值用作日期,就必須對其進行轉換:
DateTime dt;
dt = Convert.ToDateTime(Request.Cookies["userInfo"]["lastVisit"]);
Cookie 中子鍵的類型是 NameValueCollection 類型的集合。因此,另一種擷取單個子鍵的方法是先擷取子鍵集合,然後按名稱提取子鍵的值,如下所示:
if (Request.Cookies["userInfo"] != null)
{
System.Collections.Specialized.NameValueCollection UserInfoCookieCollection;
UserInfoCookieCollection = Request.Cookies["userInfo"].Values;
strOutput = Server.HtmlEncode(UserInfoCookieCollection["userName"]);
labInfo.Text = strOutput + " " + Server.HtmlEncode(UserInfoCookieCollection["lastVisit"]);
}
讀取 Cookie 集合
要讀取可供頁面使用的所有 Cookie 的名稱和值,您可以利用如下代碼遍曆 Request.Cookies 集合:
string strOutput="";
HttpCookie aCookie;
for (int i=0;i<=Request.Cookies.Count-1;i++)
{
aCookie=Request.Cookies[i];
strOutput += "<p>Cookie Name= " + Server.HtmlEncode(aCookie.Name) + "<br />";
strOutput += "Cookie Value= " + Server.HtmlEncode(aCookie.Value) + "</p>";
}
labInfo.Text=strOutput;
注意:您很可能會看到一個名為“ASP.NET_SessionId”的 Cookie,這個 Cookie 儲存您的會話的唯一識別碼。它不會永久儲存在硬碟上。
如果 Cookie 有子鍵,就會以一個單獨的名稱/值字串來顯示子鍵。Cookie 的 HasKeys 屬性可以告訴您該 Cookie 是否有子鍵。如果有子鍵,您可以在子鍵集合中向下切入,擷取各個子鍵的名稱和值。您還可以從 Cookie 屬性 Values 中擷取有關子鍵的資訊,該屬性是類型 NameValueCollection 的集合。您可以根據索引值從 Values 集合中直接讀取子索引值。相應的子索引值可以從 Values 集合的成員 AllKeys 中得到,該成員將返回一個字串集合。
以下樣本中使用 HasKeys 屬性來測試子鍵,如果檢測到子鍵,就從 Values 集合中擷取子鍵:
string strOutput="",subKeyName="",subKeyValue="";
HttpCookie aCookie;
for (int i=0;i<=Request.Cookies.Count-1;i++)
{
aCookie=Request.Cookies[i];
strOutput += "Cookie Name = " + aCookie.Name + "<br />";
if (aCookie.HasKeys)
{
for (int j=0;j<=aCookie.Values.Count-1;j++)
{
subKeyName=Server.HtmlEncode(aCookie.Values.AllKeys[j]);
subKeyValue=Server.HtmlEncode(aCookie.Values[j]);
strOutput += "<p>Sub Cookie Name = " + subKeyName + "<br />";
strOutput += "Sub Cookie Value = " + subKeyValue + "</p>";
}
}
else
strOutput += "Cookie Value = " + Server.HtmlEncode(aCookie.Value) + "<br />";
labInfo.Text = strOutput;
}
您也可以把子鍵作為 NameValueCollection 對象進行提取:
string strOutput="",subKeyName="",subKeyValue="";
HttpCookie aCookie;
for (int i=0;i<=Request.Cookies.Count-1;i++)
{
aCookie=Request.Cookies[i];
strOutput += "Cookie Name = " + aCookie.Name + "<br />";
if (aCookie.HasKeys)
{
System.Collections.Specialized.NameValueCollection CookieValues = aCookie.Values;
string[] CookieValueNames = CookieValues.AllKeys;
for (int j=0;j<=CookieValues.Count-1;j++)
{
subKeyName=Server.HtmlEncode(CookieValueNames[j]);
subKeyValue=Server.HtmlEncode(CookieValues[j]);
strOutput += "<p>Sub Cookie Name = " + subKeyName + "<br />";
strOutput += "Sub Cookie Value = " + subKeyValue + "</p>";
}
}
else
strOutput += "Cookie Value = " + Server.HtmlEncode(aCookie.Value) + "<br />";
labInfo.Text = strOutput;
}
注意:請記住,我之所以調用 Server.HtmlEncode 方法,只是因為我要在頁面上顯示 Cookie 的值。如果您只是測試 Cookie 的值,就不必在使用前對其進行編碼。
修改和刪除 Cookie
修改某個 Cookie 實際上是指用新的值建立新的 Cookie,並把該 Cookie 發送到瀏覽器,覆蓋客戶機上舊的 Cookie。
以下樣本說明了如何更改用於儲存網站訪問次數的 Cookie 的值:
int intCounter=0;
if (Request.Cookies["Counter"] != null)
intCounter = Convert.ToInt16(Request.Cookies["Counter"].Value);
intCounter++;
Response.Cookies["Counter"].Value = intCounter.ToString();
Response.Cookies["Counter"].Expires = DateTime.Now.AddDays(1);
labInfo.Text = Server.HtmlEncode(Request.Cookies["Counter"].Value);
另一種方法:
HttpCookie ctrCookie;
int intCounter=0;
if (Request.Cookies["Counter"] != null)
ctrCookie = Request.Cookies["Counter"];
else
ctrCookie = new HttpCookie("Counter");
intCounter = Convert.ToInt16(ctrCookie.Value);
intCounter++;
ctrCookie.Value = intCounter.ToString();
ctrCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(ctrCookie);
labInfo.Text = Server.HtmlEncode(Request.Cookies["Counter"].Value);
刪除 Cookie
刪除 Cookie 其實只是通過瀏覽器修改 Cookie 的一種形式。修改 Cookie 的方法上面已經介紹過(即用相同的名稱建立一個新的 Cookie),不同的是將其有效期間設定為過去的某個日期。當瀏覽器檢查 Cookie 的有效期間時,就會刪除這個已到期的 Cookie。
以下樣本比刪除單個 Cookie 要稍微有趣一些,它使用的方法可以刪除當前域的所有 Cookie:
HttpCookie aCookie;
int limit = Request.Cookies.Count - 1;
for (int i = 0; i <= limit; i++)
{
aCookie = Request.Cookies[i];
aCookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(aCookie);
labInfo.Text += "<br/>Delete " + aCookie.Name + " Done...";
}
修改或刪除子鍵
修改單個子鍵的方法與最初建立它的方法相同:
Response.Cookies["userInfo"]["lastVisit"]=DateTime.Now.ToString(); Response.Cookies["userInfo"].Expires=DateTime.Now.AddDays(1);
但是你不能簡單得重新設定 Cookie 的到期日期,因為這樣只能刪除整個 Cookie 而不能刪除單個子鍵。實際的解決方案是對包含子鍵的 Cookie 的 Values 集合進行操作。首先,通過從 Request.Cookies 對象中擷取 Cookie 來重新建立 Cookie。然後,您就可以調用 Values 集合的 Remove 方法,將要刪除的子鍵名稱傳遞到 Remove 方法。接下來,您通常可以將修改後的 Cookie 添加到 Response.Cookies 集合,以便將修改後的 Cookie 發送回瀏覽器。
string subKeyName = "userName"; //Define will be deleted Sub-Key Name.
HttpCookie aCookie = Request.Cookies["userInfo"];
aCookie.Values.Remove(subKeyName);
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);
labInfo.Text += "<br/>Delete " + aCookie.Name + "." + subKeyName + " Done...";
Cookie 與安全性
就應用程式而言,Cookie 是使用者輸入的另一種形式,因而很容易被他人非法擷取和利用。由於 Cookie 儲存在使用者自己的電腦上,所以使用者至少可以看到您儲存在 Cookie 中的資訊。如果使用者願意,還能在瀏覽器向您發送 Cookie 之前修改該 Cookie。注意:千萬不要在 Cookie 中儲存保密資訊 - 使用者名稱、密碼、信用卡號等等。在 Cookie 中不要儲存不應該由使用者掌握的內容,也不要儲存可能被其他竊取 Cookie 的人控制的內容。同樣,要對從 Cookie 中得到的任何資訊都持懷疑態度。不要認為得到的資料就是您當初設想的資訊。處理 Cookie 值時採用的安全措施應該與處理 Web 頁面中使用者鍵入的資料時採用的安全措施相同。Cookie 是以純文字的形式在瀏覽器和伺服器之間傳送的,任何可以截取 Web 通訊的人都可以讀取 Cookie。您可以對 Cookie 的屬性進行設定,使其只能在使用安全通訊端層(SSL,又稱 https://)的串連上傳輸。
面對這些安全問題,如何才能安全地使用 Cookie?您可以在 Cookie 中儲存一些不重要的資料,如使用者喜好設定或其他對應用程式沒有重大影響的資訊。如果確實需要把某些敏感資訊(如使用者識別碼)儲存在 Cookie 中,就對這些資訊進行加密。一種可行的方法是利用 ASP.NET Forms Authentication 公用程式建立一個身分識別驗證票據,作為 Cookie 儲存。
檢查瀏覽器是否接受 Cookie
以下是一個簡單的樣本來說明如何測試 Cookie 是否被接受。該樣本包含兩個頁面:在第一個頁面(Create.aspx)中,建立了一個 Cookie,然後把瀏覽器重新定向到第二個頁面。第二個頁面(Read.aspx)嘗試讀取這個 Cookie,轉而將瀏覽器重新定向到第一個頁面,並向 URL 添加一個帶有測試結果的查詢字串變數。以下是第一個頁面(Create.aspx):
private void Page_Load(object sender, System.EventArgs e)
{
if (!Page.IsPostBack)
{
if (Request.QueryString["AcceptsCookies"] == null)
{
Response.Cookies["testCookie"].Value = "OK!";
Response.Cookies["testCookie"].Expires = DateTime.Now.AddMinutes(1);
Response.Redirect("Read.aspx?redirect=" + Server.UrlEncode(Request.Url.ToString()));
}
else
labInfo.Text = "接受 Cookie = " + Request.QueryString["AcceptsCookies"];
}
}
以下是第二個接收頁面(Read.aspx):
private void Page_Load(object sender, System.EventArgs e)
{
string redirect = Request.QueryString["redirect"];
string acceptsCookies;
if (Request.Cookies["testCookie"] == null)
acceptsCookies = "0";
else
{
acceptsCookies = "1";
Response.Cookies["testCookie"].Expires = DateTime.Now.AddDays(-1);
}
Response.Redirect(redirect + "?AcceptsCookies=" + acceptsCookies,true);
}