[转]学习如何使用 Cookie 编程
什么是 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)或在正文下方的评论中给我留言。
Open Link
Cookie 的限制
大多数浏览器支持每个站点保存 20 个最多可达 4096 字节的 Cookie,如果试图保存更多的 Cookie,则最先保存的 Cookie 就会被删除。最可能遇到的 Cookie 限制是:用户可以设置自己的浏览器,拒绝接受 Cookie。尽管 Cookie 在应用程序中非常有用,应用程序也不应该依赖于能够保存 Cookie。利用 Cookie 可以做到锦上添花,但不要利用它们来支持关键功能。
编写 Cookie
您可以利用页面的 Response 属性来编写 Cookie,该属性提供的对象使用户可以将信息添加到由页面向浏览器呈现的信息中。Response 对象支持一个名为 Cookies 的集合,您可以向其中添加要写入浏览器的 Cookie。可以通过多种方法把 Cookie 添加到 Response.Cookies 集合中:
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"]["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 限制到站点上的某个应用程序,另一种是把有效范围设置为某个域,从而允许您指定域中的哪些子域可以访问。
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"].Expires=DateTime.Now.AddDays(1);
Response.Cookies["subCookie"].Domain="ruly.ryun.cn";
你用如下方法就可以把 Cookie 用于主域(ryun.cn)、和子域(ruly.ryun.cn)中:
Response.Cookies["subCookie"].Expires=DateTime.Now.AddDays(1);
Response.Cookies["subCookie"].Domain="ryun.cn";
读取 Cookie
当浏览器向服务器发送请求时,该服务器的 Cookie 会与请求一起发送。在 ASP.net 中,您可以使用 Request 对象来读取 Cookie。以下示例显示了两种方法,目的都是获取名为“username”的 Cookie 的值并将值显示在 Label 控件中:
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。
以下是获取子键值的一种方法:
if (Request.Cookies["userInfo"] != null)
{
strOutput = Server.HtmlEncode(Request.Cookies["userInfo"]["userName"]);
labInfo.Text = strOutput + " " + Server.HtmlEncode(Request.Cookies["userInfo"]["lastVisit"]);
}
Cookie 是用字符串的形式保存值的,如要将 lastVisit 值用作日期,就必须对其进行转换:
dt = Convert.ToDateTime(Request.Cookies["userInfo"]["lastVisit"]);
Cookie 中子键的类型是 NameValueCollection 类型的集合。因此,另一种获取单个子键的方法是先获取子键集合,然后按名称提取子键的值,如下所示:
{
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 集合:
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 集合中获取子键:
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 对象进行提取:
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 的值:
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);
另一种方法:
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:
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...";
}
修改或删除子键
修改单个子键的方法与最初创建它的方法相同:
但是你不能简单得重新设置 Cookie 的过期日期,因为这样只能删除整个 Cookie 而不能删除单个子键。实际的解决方案是对包含子键的 Cookie 的 Values 集合进行操作。首先,通过从 Request.Cookies 对象中获取 Cookie 来重新创建 Cookie。然后,您就可以调用 Values 集合的 Remove 方法,将要删除的子键名称传递到 Remove 方法。接下来,您通常可以将修改后的 Cookie 添加到 Response.Cookies 集合,以便将修改后的 Cookie 发送回浏览器。
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 中保存一些不重要的数据,如用户首选项或其他对应用程序没有重大影响的信息。如果确实需要把某些敏感信息(如用户 ID)保存在 Cookie 中,就对这些信息进行加密。一种可行的方法是利用 ASP.NET Forms Authentication 实用程序创建一个身份验证票据,作为 Cookie 保存。
检查浏览器是否接受 Cookie
以下是一个简单的示例来说明如何测试 Cookie 是否被接受。该示例包含两个页面:在第一个页面(Create.aspx)中,创建了一个 Cookie,然后把浏览器重新定向到第二个页面。第二个页面(Read.aspx)尝试读取这个 Cookie,转而将浏览器重新定向到第一个页面,并向 URL 添加一个带有测试结果的查询字符串变量。以下是第一个页面(Create.aspx):
{
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):
{
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);
}