3.4 ASP.NET Cookie
学习目标
学习并掌握Cookie的使用方法。
3.4.1 Cookie概述
Cookie提供了一种在Web应用程序中存储用户特定信息(如历史记录或用户首选项)的方法。Cookie是一小段文本,伴随着请求和响应在Web服务器和客户端之间来回传输。Cookie包含每次用户访问站点时可由Web应用程序读取的信息。Cookies集合是Response对象和Request对象共有的一项经常用到的集合,专门用于操作Cookie信息。
通常用户在通过HTTP协议访问一个主页时,每次连接时都要重新开始。因此,如果要判别某个用户是否曾经进入本网站,那么只能使用Cookies了。当用户第一次访问网站时,可以利用Response对象的Cookies集合将数据存储到客户端计算机,当用户再次访问此网站时,则可利用Request对象的Cookies集合取得相关信息。
浏览器管理客户端计算机上的Cookie。使用HttpResponse对象可向客户端发送Cookie,该对象会公开称为Cookies的属性。任何需要Web应用程序发送到浏览器的Cookie都必须添加到此集合中。编写新Cookie时,必须指定Name和Value。每个Cookie必须有一个唯一的名称,这样在以后当浏览器随同请求发送Cookie时,Web应用程序就可以标识该Cookie。
有两种方法可以向用户计算机写Cookie。可以直接为Cookies集合设置Cookie属性,也可以创建HttpCookie对象的一个实例并将该实例添加到Cookies集合中。必须在ASP.NET页呈现给客户端之前创建Cookie。例如,可以在Page_Load事件处理程序中编写Cookie,但不能在Page_Unload事件处理程序中编写Cookie。
例如,如果在用户请求站点中的页面时应用程序发送给该用户的不仅仅是一个页面,还有一个包含日期和时间的Cookie,用户的浏览器在获得页面的同时还获得了该Cookie,并将它存储在用户硬盘上的某个文件夹中。
以后,如果该用户再次请求此站点中的页面,当该用户输入URL时,浏览器便会在本地硬盘上查找与该URL关联的Cookie。如果该Cookie存在,浏览器便将该Cookie与页请求一起发送到此站点。然后,应用程序便可以确定该用户上次访问站点的日期和时间。可以使用这些信息向用户显示一条消息,也可以检查到期日期。
Cookie与网站关联,而不是与特定的页面关联。因此,无论用户请求站点中的哪一个页面,浏览器和服务器都将交换Cookie信息。用户访问不同站点时,各个站点都可能会向用户的浏览器发送一个Cookie,浏览器会分别存储所有Cookie。
Cookie帮助网站存储有关访问者的信息。一般来说,Cookie是一种保持Web应用程序连续性(即执行状态管理)的方法。除短暂的实际交换信息的时间外,浏览器和Web服务器间都是断开连接的。对于用户向 Web服务器发出的每个请求,Web服务器都会单独处理。但是在很多情况下,Web服务器在用户请求页时识别出用户会十分有用。例如,购物站点上的Web服务器跟踪每位购物者,这样站点就可以管理购物车和其他的用户特定信息。因此,Cookie可以作为一种名片,提供相关的标识信息帮助应用程序确定如何继续执行。
使用Cookie能够达到多种目的,所有这些目的都是为了帮助网站记住用户。例如,一个实施民意测验的站点可以简单地将Cookie作为一个Boolean值,用它来指示用户的浏览器是否已参与了投票,这样用户便无法进行第二次投票。要求用户登录的站点则可以通过Cookie来记录用户已经登录,这样用户就不必每次都输入凭据。
3.4.2 Cookie的限制
大多数浏览器支持最大为4096字节的Cookie。由于这限制了Cookie的大小,最好用Cookie来存储少量数据,或者存储用户ID之类的标识符。用户ID随后便可用于标识用户,以及从数据库或其他数据源中读取用户信息。
3.4.3 写入Cookie
浏览器负责管理用户系统上的Cookie。Cookie通过HttpResponse对象发送到浏览器,该对象公开称为Cookies的集合。可以将HttpResponse对象作为Page类的Response属性来访问。要发送给浏览器的所有Cookie都必须添加到此集合中。创建Cookie时,需要指定Name和Value。每个Cookie必须有一个唯一的名称,以便以后从浏览器读取Cookie时可以识别它。由于Cookie按名称存储,因此用相同的名称命名两个Cookie会导致其中一个Cookie被覆盖。
还可以设置Cookie的到期日期和时间。用户访问编写Cookie的站点时,浏览器将删除过期的Cookie。只要应用程序认为Cookie值有效,就应将Cookie的有效期设置为这一段时间。对于永不过期的Cookie,可将到期日期设置为从现在起 50 年。
用户可随时清除其计算机上的Cookie。即便存储的Cookie距到期日期还有很长时间,但用户还是可以决定删除所有Cookie,清除Cookie中存储的所有设置,因为Cookie信息默认就保存在客户端机器的“C:\Documents and Settings\Administrator\Cookies”路径中,用户当然可以随时人为删除。
如果没有设置Cookie的有效期,仍会创建Cookie,但不会将其存储在用户的硬盘上。而会将Cookie作为用户会话信息的一部分进行维护。当用户关闭浏览器时,Cookie便会被丢弃。这种非永久性Cookie很适合用来保存只需短时间存储的信息,或者保存由于安全原因不应该写入客户端计算机的磁盘的信息。例如,如果用户在使用一台公用计算机,而不希望将Cookie写入该计算机的磁盘中,这时就可以使用非永久性Cookie。
可以通过多种方法将Cookie添加到Cookies集合中。下面的示例给出两种编写Cookie的方法。
方法1:
Response.Cookies["userName"].Value = "admin"; Response.Cookies["userName"].Expires = DateTime.Now.AddDays(1);
方法2:
HttpCookie aCookie = new HttpCookie("lastVisit"); aCookie.Value = DateTime.Now.ToString(); aCookie.Expires = DateTime.Now.AddDays(1); Response.Cookies.Add(aCookie);
此示例向Cookies集合添加两个Cookie,一个名为userName,另一个名为lastVisit。对于第一个Cookie,Cookies集合的值是直接设置的。可以通过这种方式向集合添加值。对于第二个Cookie,代码创建了一个HttpCookie类型的对象实例,设置其属性,然后通过Add方法将其添加到Cookies集合。在实例化HttpCookie对象时,必须将该Cookie的名称作为构造函数的一部分进行传递。
这两个示例都完成了同一任务,即向浏览器写入一个Cookie。在这两种方法中,有效期值必须为DateTime类型。但是,lastVisited值也是日期时间值。因为所有Cookie值都存储为字符串,因此,必须将日期时间值转换为String。
3.4.4 多值Cookie
可以在Cookie中存储一个值,如用户名和上次访问时间。也可以在一个Cookie中存储多个名称/值对。名称/值对称为子键。例如,不要创建两个名为userName和lastVisit的单独Cookie,而可以创建一个名为userInfo的Cookie,其中包含两个子键userName和lastVisit。
可能会出于多种原因来使用子键。首先,将相关或类似的信息放在一个Cookie中很方便。此外,由于所有信息都在一个Cookie中,所以诸如有效期之类的Cookie属性就适用于所有信息。反之,如果要为不同类型的信息指定不同的到期日期,就应该把信息存储在单独的Cookie中。
带有子键的Cookie还可限制Cookie文件的大小。正如3.4.2节中所提到的,Cookie通常限制为4096字节,并且每个站点最多可存储20个Cookie。使用带子键的单个Cookie,使用的Cookie数就不会超过分配给站点的20个的限制。
若要创建带子键的Cookie,可以使用编写单个Cookie的各种语法。下面的示例给出编写同一Cookie的两种方法,其中的每个Cookie都带有两个子键。
方法1:
Response.Cookies["userInfo"]["userName"] = "patrick"; Response.Cookies["userInfo"]["lastVisit"] = DateTime.Now. ToString(); Response.Cookies["userInfo"].Expires = DateTime.Now.AddDays(1);
方法2:
HttpCookie aCookie = new HttpCookie("userInfo"); aCookie.Values["userName"] = "patrick"; aCookie.Values["lastVisit"] = DateTime.Now.ToString(); aCookie.Expires = DateTime.Now.AddDays(1); Response.Cookies.Add(aCookie);
3.4.5 控制Cookie的范围
默认情况下,一个站点的全部Cookie都一起存储在客户端上,而且所有Cookie都会随着对该站点发送的任何请求一起发送到服务器。也就是说,一个站点中的每个页面都能获得该站点的所有Cookie。但是,可以通过两种方式设置Cookie的范围:将Cookie的范围限制到服务器上的某个文件夹,这允许将Cookie限制到站点上的某个应用程序;将范围设置为某个域,这允许指定域中的哪些子域可以访问Cookie。
1.将Cookie限制到某个文件夹或应用程序
若要将Cookie限制到服务器上的某个文件夹,可以按下面的示例设置Cookie的Path属性:
HttpCookie appCookie = new HttpCookie("AppCookie"); appCookie.Value = "written " + DateTime.Now.ToString(); appCookie.Expires = DateTime.Now.AddDays(1); appCookie.Path = "/Application1"; Response.Cookies.Add(appCookie);
还可以通过将Cookie直接添加到Cookies集合的方式来编写Cookie,如先前的示例所示。
路径可以是站点根目录下的物理路径,也可以是虚拟根目录。所产生的效果是Cookie只能用于 Application1 文件夹或虚拟根目录中的页面。例如,如果站点名称为www.abc.com,则在前面示例中创建的Cookie将只能用于路径为 http://www.abc.com/Application1/ 的页面以及该文件夹下的所有页面。但是,Cookie将不能用于其他应用程序中的页面,如 http://www.abc.com/Application2/ 或 http://www.abc.com/ 中的页面。
在某些浏览器中,路径区分大小写。开发人员无法控制用户如何在其浏览器中输入URL,但如果应用程序依赖于与特定路径相关的Cookie,就要确保创建的所有超链接中的URL与Path属性值的大小写相匹配。
2.限制Cookie的域范围
默认情况下,Cookie与特定域关联。例如,如果站点是 www.abc.com,那么当用户向该站点请求任何页时,编写的Cookie就会被发送到服务器(这可能不包括带有特定路径值的Cookie)。如果站点具有子域(例如,abc.com、sales.abc.com和support.abc.com),则可以将Cookie与特定的子域关联。若要执行此操作,可以设置Cookie的Domain属性,如下面示例所示:
Response.Cookies["domain"].Value = DateTime.Now.ToString(); Response.Cookies["domain"].Expires = DateTime.Now.AddDays(1); Response.Cookies["domain"].Domain = "support.abc.com";
当以此方式设置域时,Cookie将仅可用于指定的子域中的页面。还可以使用Domain属性创建可在多个子域间共享的Cookie,如下面的示例所示:
Response.Cookies["domain"].Value = DateTime.Now.ToString(); Response.Cookies["domain"].Expires = DateTime.Now.AddDays(1); Response.Cookies["domain"].Domain = "abc.com";
随后Cookie将可用于主域,也可用于sales.abc.com和support.abc.com域。
3.4.6 读取Cookie
浏览器向服务器发出请求时,会随请求一起发送该服务器的Cookie。在ASP.NET应用程序中,可以使用Request对象读取Cookie,下面的代码示例给出了读取Cookie的方法,通过这种方法可获取名为username的Cookie的值,并输出。
if (Request.Cookies["userName"] != null) { Response.Write(Server.HtmlEncode(Request.Cookies["userName"].Value)); }
在尝试获取Cookie的值之前,应确保该Cookie存在;如果该Cookie不存在,将会收到异常错误。还应注意在页面中显示Cookie的内容前,先调用HtmlEncode方法对Cookie的内容进行编码。这样可以确保恶意用户没有向Cookie中添加可执行脚本。
读取Cookie中子键值的方法与设置该值的方法类似。下面的代码示例给出了获取子键值的一种方法:
if (Request.Cookies["userInfo"] != null) { Label1.Text = Server.HtmlEncode(Request.Cookies["userInfo"] ["userName"]); Label2.Text = Server.HtmlEncode(Request.Cookies["userInfo"] ["lastVisit"]); }
在上面的示例中,代码读取子键lastVisit的值,该值是先前被设置为字符串表示形式的DateTime值。Cookie将值存储为字符串,因此,如果要将lastVisit值作为日期使用,必须将其转换为适当的类型,如下面示例所示:
DateTime dt; dt = DateTime.Parse(Request.Cookies["userInfo"]["lastVisit"]);
3.4.7 修改Cookie
不能直接修改Cookie。更改Cookie的过程涉及创建一个具有新值的新Cookie,然后将其发送到浏览器来覆盖客户端上的旧版本Cookie。下面的代码示例演示如何更改存储用户对站点的访问次数的Cookie的值:
int counter; if (Request.Cookies["counter"] == null) counter = 0; else { counter = int.Parse(Request.Cookies["counter"].Value); } counter++; Response.Cookies["counter"].Value = counter.ToString(); Response.Cookies["counter"].Expires = DateTime.Now.AddDays(1);
3.4.8 删除Cookie
删除Cookie(即从用户的硬盘中物理移除Cookie)是修改Cookie的一种形式。由于Cookie在用户的计算机中,因此无法将其直接移除。但是,可以让浏览器来删除Cookie。该技术是创建一个与要删除的Cookie同名的新Cookie,并将该Cookie的到期日期设置为早于当前日期的某个日期。当浏览器检查Cookie的到期日期时,浏览器便会丢弃这个现已过期的Cookie。下面的代码示例给出了删除应用程序中所有可用Cookie的一种方法。
HttpCookie aCookie; string cookieName; int limit = Request.Cookies.Count; for (int i=0; i<limit; i++) { cookieName = Request.Cookies[i].Name; aCookie = new HttpCookie(cookieName); aCookie.Expires = DateTime.Now.AddDays(-1); Response.Cookies.Add(aCookie); }
当然如果只是删除指定的Cookie就只要设置指定Cookie的到期日期为早于当前日期的某个日期,下面的代码示例给出了删除指定Cookie的方法。
Response.Cookies["userName"].Expires = DateTime.Now.AddDays(-1);
3.4.9 Cookie和安全性
Cookie的安全性问题与从客户端获取数据的安全性问题类似。在应用程序中,Cookie是另一种形式的用户输入,因此很容易被非法获取和利用。由于Cookie保存在用户自己的计算机上,因此,用户至少能看到存储在Cookie中的数据。用户还可以在浏览器向服务器发送Cookie之前更改该Cookie。
不要在Cookie中存储敏感信息,如用户名、密码、信用卡号等。不要在Cookie中放置任何不应由用户掌握的内容,也不要放可能被其他窃取Cookie的人控制的内容。
同样,不要轻信从Cookie中得到的信息。不要假定数据与写出时相同,处理Cookie值时采用的安全措施应该与处理网页中用户输入的数据时采用的安全措施相同。本主题前面的示例演示在页面中显示值前,先对Cookie内容进行HTML编码的方法,这与在显示从用户处得到的任何信息之前的做法相同。
Cookie以明文形式在浏览器和服务器间发送,任何可以截获Web通信的人都可以读取Cookie。可以设置Cookie属性,使Cookie只能在使用安全套接字层(SSL)的连接上传输。SSL并不能防止保存在用户计算机上的Cookie被读取或操作,但可防止Cookie在传输过程中被读取,因为Cookie已被加密。
3.4.10 实例分析:使用Cookie完成一个会员登录系统
用户在通过HTTP协议访问一个主页时,每次连接时都要重新开始。因此,如果要判别某个用户是否曾经进入本网站,那么只能使用Cookies了。当用户第一次访问网站时,可以利用Response对象的Cookies集合将数据存储到客户端计算机,当用户再次访问此网站时,则可利用Request对象的Cookies集合取得相关信息。网络上很多论坛、购物车的登录系统就是使用Cookie来保存用户登录信息的。
本实例要给出的正是这种登录系统使用Cookie保存用户信息的实现方法,需要用到用户表信息,接下来先给出用户数据库及表的创建脚本代码。
create database userDB go use userDB go create table userTable ( Username varchar(50) primary key, Password varchar(50) not null ) insert into userTable values('admin','1234') insert into userTable values('abcde','abcde') go
(1)在ASP.NET网站项目StudyObject中添加页面Login.aspx,并在工具箱中的“标准”选项卡中选择相关控件添加到页面,布局页面如图3-9所示。
(2)在图3-9中直接双击“登录”按钮进入该按钮的单击事件代码编写窗口,编写登录处理代码,接下来给出完成登录处理代码,同时给出Page_Load初始化Cookie保存下拉列表框数据项的代码(注意:Login.aspx.cs中必须导入操作数据SQL Server的命名空间,如"using System.Data.SqlClient;")。
protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { ddlInterval.Items.Add(new ListItem("不保存","0")); ddlInterval.Items.Add(new ListItem("保存1天","1")); ddlInterval.Items.Add(new ListItem("保存1周","7")); ddlInterval.Items.Add(new ListItem("保存1月","30")); ddlInterval.Items.Add(new ListItem("保存1年","365")); ddlInterval.Items[0].Selected = true; } }//end Page_Load protected void btnConfirm_Click(object sender, EventArgs e) { string Username = txtUsername.Text.Trim(); string Password = txtPassword.Text.Trim(); string Interval = ddlInterval.SelectedValue; SqlConnection conn = new SqlConnection(); conn.ConnectionString = @"server=DUANKEQI;uid=sa;pwd=sa ;database=userDB"; string sqlString = "select Username,Password from userTable where Username='" + Username + "'"; SqlDataAdapter sda = new SqlDataAdapter(sqlString, conn); DataSet ds = new DataSet(); sda.Fill(ds); if (ds.Tables[0].Rows.Count==0) { //说明输入的用户名不存在 Response.Write("<script language='javascript'>alert(' 用户名称输入错误');history.back();</script>"); Response.End(); } else { //说明输入的用户名存在,再判断口令是否正确 string Password_db = ds.Tables[0].Rows[0]["Password"]. ToString(); if (Password==Password_db) { //说明口令正确,于是定义Cookie变量以及有效期 Response.Cookies["Username"].Value = Username; //如果用户选择了不保存则不定义有效期,否则定义有效期 if (Convert.ToInt32(Interval) > 0) { Response.Cookies["Username"].Expires = DateTime. Now.AddDays(Convert.ToInt32(Interval)); } //处理完毕后重定向到登录成功页面,比如为Success.aspx Response.Redirect("Success.aspx"); } else { //说明口令不正确 Response.Write("<script language='javascript'>alert(' 口令输入错误’);history.back();</script>”); Response.End(); } } }//btnConfirm_Click
(3)在ASP.NET网站项目StudyObject中再添加页面Success.aspx,作为登录成功后显示的页面,并在工具箱中的“标准”选项卡中选择相关控件添加到页面,布局页面如图3-10所示。
(4)在图3-10中右击,在弹出的快捷菜单中选择“查看代码”命令,进入Success.aspx.cs代码编写窗口,编写Page_Load代码如下。
protected void Page_Load(object sender, EventArgs e) { string Username = string.Empty; if (Request.Cookies["Username"] == null) { Username = ""; } else { Username = Request.Cookies["Username"].Value; } if (Username == "") { Response.Write("<script language='javascript'>alert(' 你没有登录不可以查看此页面');location.href='Login.aspx'; </script>"); Response.End(); } lblUsername.Text = Username; }
(5)在网页Success.aspx中直接按Ctrl+F5组合键,页面首次运行效果如图3-11所示。
(6)在图3-11中单击“确定”按钮直接进入登录页面Login.aspx,如图3-12所示。
(7)如果输入错误的用户名和口令都会提示错误,最终返回到Login.aspx页面,正确输入用户名称“admin”和口令“1234”后,并选择“保存1周”,单击“登录”按钮显示的即是登录成功后页面Success.aspx显示的效果,如图3-13所示。
(8)而且由于选择了保存一周,实际上就是把这个Cookie信息保存到了客户端的“C:\Documents and Settings\Administrator\Cookies”位置下,如果开始该文件夹没有任何Cookie信息的文本文件,那么登录成功后则显示如图3-14所示,当然此时用户就是重启了自己的计算机都可以不用登录而直接进入Success.aspx页面,因为用户信息就在客户端用户自己的机器磁盘中,当然用户如果人为删除该文件那就无效了。另外登录成功页面严格讲还应做一个退出按钮,退出按钮事件代码就是让Cookie失效即可,当然失效后图3-14中的文本文件就被删除了。定义Cookie失效的代码如下:
Response.Cookies["Username"].Expires = DateTime.Now.AddDays(-1);