{{ v.name }}
{{ v.cls }}类
{{ v.price }} ¥{{ v.price }}
阅读目录
开始
cookie概述
cookie的写、读过程
使用cookie保存复杂对象
js中读写cookie
cookie在session中的应用
cookie在身份验证中的应用
cookie的安全状况
如何在c#发请的请求中使用cookie
重构与使用总结
补充
cookie虽然是个很简单的东西,但它又是web开发中一个很重要的客户端数据来源,而且它可以实现扩展性很好的会话状态,所以我认为每个web开发人员都有必要对它有个清晰的认识。本文将对cookie这个话题做一个全面的描述,也算是我对cookie的认识总结。
回到顶部
cookie是什么?cookie是一小段文本信息,伴随着用户请求和页面在web服务器和浏览器之间传递。cookie包含每次用户访问站点时web应用程序都可以读取的信息。
为什么需要cookie?因为http协议是无状态的,对于一个浏览器发出的多次请求,web服务器无法区分是不是来源于同一个浏览器。所以,需要额外的数据用于维护会话。cookie正是这样的一段随http请求一起被传递的额外数据。
cookie能做什么?cookie只是一段文本,所以它只能保存字符串。而且浏览器对它有大小限制以及它会随着每次请求被发送到服务器,所以应该保证它不要太大。cookie的内容也是明文保存的,有些浏览器提供界面修改,所以,不适合保存重要的或者涉及隐私的内容。
cookie的限制。大多数浏览器支持最大为4096字节的cookie。由于这限制了cookie的大小,最好用cookie来存储少量数据,或者存储用户id之类的标识符。用户id随后便可用于标识用户,以及从数据库或其他数据源中读取用户信息。浏览器还限制站点可以在用户计算机上存储的cookie的数量。大多数浏览器只允许每个站点存储20个cookie;如果试图存储更多cookie,则最旧的cookie便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的cookie总数作出绝对限制,通常为300个。
通过前面的内容,我们了解到cookie是用于维持服务端会话状态的,通常由服务端写入,在后续请求中,供服务端读取。下面本文将按这个过程看看cookie是如何从服务端写入,最后如何传到服务端以及如何读取的。
回到顶部
在asp.net中,读写cookie是通过使用httpcookie类来完成的,它的定义如下:
publicsealedclasshttpcookie{//获取或设置将此cookie与其关联的域。默认值为当前域。publicstringdomain{get;set;}//获取或设置此cookie的过期日期和时间(在客户端)。publicdatetimeexpires{get;set;}//获取一个值,通过该值指示cookie是否具有子键。publicboolhaskeys{get;}//获取或设置一个值,该值指定cookie是否可通过客户端脚本访问。//如果cookie具有httponly属性且不能通过客户端脚本访问,则为true;否则为false。默认为false。publicboolhttponly{get;set;}//获取或设置cookie的名称。publicstringname{get;set;}//获取或设置要与当前cookie一起传输的虚拟路径。默认值为当前请求的路径。publicstringpath{get;set;}//获取或设置一个值,该值指示是否使用安全套接字层(ssl)(即仅通过https)传输cookie。publicboolsecure{get;set;}//获取或设置单个cookie值。默认值为空引用。publicstringvalue{get;set;}//获取单个cookie对象所包含的键值对的集合。publicnamevaluecollectionvalues{get;}//获取system.web.httpcookie.values属性的快捷方式。publicstringthis[stringkey]{get;set;}}
cookie写入浏览器的过程:我们可以使用如下代码在asp.net项目中写一个cookie并发送到客户端的浏览器(为了简单我没有设置其它属性)。
httpcookiecookie=newhttpcookie("mycookiename","stringvalue");response.cookies.add(cookie);
我想很多人都写过类似的代码,但是,大家有没有想过:cookie最后是如何发送到客户端的呢?我们打开fiddler来看一下吧。
从上图,您应该能发现,我们在服务端写的cookie,最后其实是通过http的响应头这种途径发送到客户端的。每一个写入动作,都会产生一个【set-cookie】的响应头。浏览器正是在每次获取请求的响应后,检查这些头来接收cookie的。
asp.net获取cookie的过程:我们可以使用如下代码在asp.net项目中读取一个cookie
httpcookiecookie=request.cookies["mycookiename"];if(cookie!=null)labcookie1.text=cookie.value;elselabcookie1.text="未定义";
代码同样也很简单,还是类似的问题:大家有没有想过,cookie是如何传到服务端的呢?我们还是继续使用fiddler来寻找答案吧。
从图片中,我们可以发现,cookie是放在请求头中,发送到服务端的。如果你一直刷新页面,就能发现,每次http请求,cookie都会被发送。当然了,浏览器也不是发送它所接收到的所有cookie,它会检查当前要请求的域名以及目录,只要这二项目与cookie对应的domain和path匹配,才会发送。对于domain则是按照尾部匹配的原则进行的。所以,我在访问www.cnblogs.com时,浏览器并不会将我在浏览www.163.com所接收到的cookie发出去。
删除cookie:其实就是在写cookie时,设置expires为一个【早于现在时间的时间】。也就是:设置此cookie已经过期,浏览器接收到这个cookie时,便会删除它们。
httpcookiecookie=newhttpcookie("mycookiename",null);cookie.expires=newdatetime(1900,1,1);response.cookies.add(cookie);
回到顶部
前面的示例代码大致演示了cookie的读写操作。不过,我们平时可能希望将更复杂的【自定义类型】通过cookie来保存,那么又该如何操作呢?对于这个问题,我们定义一个类型来看看如何处理。
publicclassdisplaysettings{publicintstyle;publicintsize;publicoverridestringtostring(){returnstring.format("style={0},size={1}",this.style,this.size);}}
上面的代码,我定义一个类型,用于保存用户在浏览页面时的显示设置。接下来,我将介绍二种方法在cookie中保存并读取它们。
方法-1,经典做法。(注意前面给出的httpcookie定义代码中的最后二个成员)
privatevoidwritecookie_2a(){displaysettingssetting=newdisplaysettings{style=1,size=24};httpcookiecookie=newhttpcookie("displaysettings1");cookie["style"]=setting.style.tostring();cookie["size"]=setting.size.tostring();response.cookies.add(cookie);}privatevoidreadcookie_2a(){httpcookiecookie=request.cookies["displaysettings1"];if(cookie==null)labdisplaysettings1.text="未定义";else{displaysettingssetting=newdisplaysettings();setting.style=cookie["style"].trytoint();setting.size=cookie["size"].trytoint();labdisplaysettings1.text=setting.tostring();}}
方法-2,将对象json序列化为字符串。
privatevoidwritecookie_2b(){displaysettingssetting=newdisplaysettings{style=2,size=48};httpcookiecookie=newhttpcookie("displaysettings2",setting.tojson());response.cookies.add(cookie);}privatevoidreadcookie_2b(){httpcookiecookie=request.cookies["displaysettings2"];if(cookie==null)labdisplaysettings2.text="未定义";else{displaysettingssetting=cookie.value.fromjson
这段代码使用了我定义的二个扩展方法。
对于这二种方法,我个人更喜欢后者,因为它具有更好扩展性:如果类型增加了成员,不需要修改读写cookie的代码。不过,这种方式产生的有些字符,比如【双引号】,极少数浏览器(opera)不支持,所以需要做urlencode或者base64编码处理。同理,对于第一种方法,遇到value有【双引号】时,我们同样需要做urlencode或者base64编码处理。
回到顶部
cookie并非只能在服务端读写,在客户端的浏览器中也可以实现对它的读写访问。而且在js中创建的cookie对于服务端仍然有效(可见),接下来我们来看看在js中如何写入cookie,演示代码将创建一个按钮,并在点击按钮后写入cookie
在js中写cookie很简单,只要给document.cookie赋值一个cookie字符串即可,至于格式,可以参考前面用fiddle看到的结果。
再来看一下如何使用js读取cookie吧。请参考如下代码:
仍然是访问document.cookie,不过,这次我们得到却是全部的cookie值,每个key/value项用分号分开,中间则用等号分开。所以,如果您想在js中读取cookie,一定要按照这个规则来拆分并解析您要读取的cookie项。鉴于这样的操作有些繁琐,我们可以jquery.cookie.js插件来轻松完成这个功能,有兴趣的朋友也可以看一下它是如何处理的。这个插件的代码比较少,这里就直接贴出,
注意哦:前面我们看到了httpcookie有个httponly属性,如果它为true,那么js是读不到那个cookie的,也就是说:我们如果在服务端生成的cookie不希望在js中能被访问,可以在写cookie时,设置这个属性。不过,通过一些工具,还是可以看到它们。
接下来,我们再来看看asp.net中cookie有哪些应用。
回到顶部
在asp.net中,httpcontext,page对象都有个session的对象,我们可以使用它来方便地在服务端保存一些与会话相关的信息。前面我们也提到过,http协议是无状态的,对于一个浏览器发出的多次请求,web服务器无法区分是不是来源于同一个浏览器。所以,为了实现会话,服务端需要一个会话标识id能保存到浏览器,让它在后续的请求时都带上这个会话标识id,以便让服务端知道某个请求属于哪个会话,这样便可以维护与会话相关的状态数据。由于cookie对于用户来说,是个不可见的东西,而且每次请求都会传递到服务端,所以它就是很理想的会话标识id的保存容器。在asp.net中,默认也就是使用cookie来保存这个id的。注意:虽然asp.net2.0也支持无cookie的会话,但那种方式要修改url,也有它的缺点,因此这种方法并没有广泛的使用。本文将不对这个话题做过多的分析,就此略过无cookie会话这种方式。
我们来看看session是如何使用cookie来保存会话标识id的,在默认的asp.net配置中,web.config有着如下定义:
如果我们执行以下操作:
session["key1"]=datetime.now;
此时,我们可以使用一些浏览器提供的工具来查看一下现在的cookie情况。
从图片上看,这个cookie的名字就是我们在配置文件中指出的名称,我们可以修改一下配置文件:
再来执行上面的写session的操作,然后看cookie
我们可以看到:sk的cookie出现了。说明:在截图时我把名称为"asp.net_sessionid"的cookie删除了。
通过上面示例,我们可以得到结论,session的实现是与cookie有关的,服务端需要将会话标识id保存到cookie中。这里再一次申明,除非你使用无cookie的会话模式,否则session是需要cookie的支持。反过来,cookie并不需要session的支持。
回到顶部
我想很多人都在asp.net的开发中使用过form身份认证。对于一个用户请求,我们可以在服务端很方便地判断它是不是代表一个已登录用户。
this.labstatus.text=(request.isauthenticated?"已登录":"未登录");
那么,您有没有好奇过:asp.net是如何识别一个请求是不是一个已登录用户发起的呢?说到这里,我们就要从用户登录说起了。为了实现登录及form认证方式,我们需要如下配置:
接下来,我们需要实现用户登录逻辑。具体实现方式有很多,不过,最终的调用都是差不多的,如下代码所示:
privatevoidsetlogin(){system.web.security.formsauthentication.setauthcookie("fish",false);}
只要执行了以上代码,我们就可以看到,前面的判断【request.isauthenticated】返回true,最终会显示"已登录"。为了探寻这个秘密,我们还是来看一下当前页面的cookie情况。
果然,多出来一个cookie,名称与我在配置文件中指定的名称相同。我们再来看看如果注销当前登录会是什么样子的:
privatevoidsetlogout(){system.web.security.formsauthentication.signout();}
看到了吗,名为"userstatus"的cookie不见了。此时如果你再去观察【request.isauthenticated】,可以发现它此时返回false。或者,您也可以再试一次,登录后,直接删除名为"userstatus"的cookie,也能发现登录状态将显示"未登录"。或许,您还是有点不清楚前面我调用【system.web.security.formsauthentication.setauthcookie("fish",false);】后,asp.net做了些什么,回答这个问题其实很简单:自己用reflector.exe去看一下asp.net的实现吧。这里为了更让您能信服登录与cookie有关,我将直接创建一个cookie看一下asp.net能不能认可我创建的cookie,并认为登录有效。请看代码:
privatevoidsetlogin(){//system.web.security.formsauthentication.setauthcookie("fish",false);//下面的代码和上面的代码在作用上是等效的。formsauthenticationticketticket=newformsauthenticationticket(2,"fish",datetime.now,datetime.now.adddays(30d),false,string.empty);stringstr=formsauthentication.encrypt(ticket);httpcookiecookie=newhttpcookie(formsauthentication.formscookiename,str);response.cookies.add(cookie);}
如果执行这段代码,您将发现:【request.isauthenticated】返回true,登录状态会显示"已登录"。至此,我们可以得出一个结论:form身份认证依赖cookie,asp.net就是每次检查我们在配置文件中指定的cookie名称,并解密这个cookie来判断当前请求用户的登录状态。
回到顶部
从以上图片,您应该能发现:浏览器能提供一些界面让用户清楚的观察我们在服务端写的cookie,甚至有些浏览器还提供很方便的修改功能。如下图所示:
所以,我们在服务端写代码读取cookie时,尤其是涉及类型转换、反序列化或者解密时,一定要注意这些操作都有可能会失败。而且上图也清楚的反映了一个事实:cookie中的值都是“一目了然”的,任何人都能看到它们。所以,我们尽量不要直接在cookie中保存一些重要的或者敏感的内容。如果我们确实需要使用cookie保存一些重要的内容,但又不希望被他人看懂,我们可以使用一些加密的方法来保护这些内容。
1.对于一些重要性不高的内容,我们可以使用base64之类的简单处理方式来处理。
2.对于重要性相对高一点的内容,我们可以利用.net提供的一些加密工具类,自己来设计加密方法来保护。不过,密码学与加密解密并不是很简单的算法,因此,自己设计的加密方式可能不会很安全。
3.重要的内容,我们可以使用.net提供的formsauthenticationticket,formsauthentication来加密。我认为这种方式还是比较安全的。毕竟前面我们也看过了,asp.net的form身份认证就是使用这种方式来加密用户登录的身份标识的,所以,如果这种方式不安全,也就意味着asp.net的身份认证也不安全了。如果您使用这种方式来加密,那么请注意:它产生的加密后文本还是比较大的,前面我也提到过,每次请求时,浏览器都会带上与请求相匹配的所有cookie,因此,这种cookie会对传输性能产生一定的影响,所以,请小心使用,切记不可过多的使用。
这里要补充一下:去年曾经出现过【paddingoracleattack】这个话题,一些人甚至错误的认为是asp.net加密方式不安全!如果您也是这样认为的,那么可以看一下这篇文章:浅谈这次asp.net的paddingoracleattack相关内容,以消除这个错误的认识。当然了,我们也可以从这个话题得到一些收获:解密失败时,不要给出过多的提示,就当没有这个cookie存在。
回到顶部
前面我们一直在谈服务端与浏览器中使用cookie,其实浏览器也是一个普通的应用程序,.netframework也提供一些类也能让我们直接发起http请求,下面我们来看一下如何在c#发请的请求中使用cookie,其实也很简单,主要是使用了cookiecontainer类,请看以下演示代码:
privatestaticstringsendhttprequestget(stringurl,encodingencoding,cookiecontainercookiecontainer){if(string.isnullorempty(url))thrownewargumentnullexception("url");if(encoding==null)thrownewargumentnullexception("encoding");httpwebrequestrequest=(httpwebrequest)webrequest.create(url);request.method="get";request.cookiecontainer=cookiecontainer;using(webresponseresponse=request.getresponse()){using(streamreaderreader=newstreamreader(response.getresponsestream(),encoding)){returnreader.readtoend();}}}privatevoidsendhttpdemo(){stringbuildersb=newstringbuilder();cookiecontainercookiecontainer=newcookiecontainer();stringurl="http://www.taobao.com";sendhttprequestget(url,encoding.default,cookiecontainer);//后面可以继续发起http请求,此时将会包含上次从服务器写入的cookie//sendhttprequestget("同域名下的其它url",encoding.default,cookiecontainer);//至此,我们可以显示取得了哪些cookiecookiecollectioncookies=cookiecontainer.getcookies(newuri(url));if(cookies!=null){foreach(system.net.cookiecookieincookies)sb.appendline(cookie.tostring());}txtcookies.text=sb.tostring();}
回到顶部
在前面的asp.net示例代码中,我一直使用.net提供的httpcookie类来操作cookie,是为了展示用原始的方式来使用cookie,这些代码有点重复,也有点繁琐,为此,我提供了几个简单的方法可以更容易的使用cookie,也算是对cookie使用的一个总结。
///
更完整的代码可以从本文的示例代码中获得。(文章底部有下载地址)
使用方式:
publicstaticclasstestclass{publicstaticvoidwrite(){stringstr="中国";intaa=25;displaysettingssetting=newdisplaysettings{style=3,size=50};datetimedt=newdatetime(2012,1,1,12,0,0);str.writecookie("key1",datetime.now.adddays(1d));aa.writecookie("key2",null);setting.tojson().writecookie("key3",null);dt.writecookie("key4",null);}publicstaticvoidread(){httprequestrequest=httpcontext.current.request;stringstr=request.cookies["key1"].getstring();intnum=request.cookies["key2"].toint(0);displaysettingssetting=request.cookies["key3"].fromjson
注意哦:以上代码中都是直接使用字符串"key"的形式,这种方式对于大一些的程序在后期可能会影响维护。所以建议:将访问cookie所使用的key能有一个类来统一的定义,或者将读写操作包装成一些属性放在一个类中统一的管理。
publicstaticclasscookievalues{//建议把cookie相关的参数放在一起,提供get/set属性(或者方法)来访问,以避免"key"到处乱写publicstaticstringaaa{get{returnhttpcontext.current.request.cookies["key1"].getstring();}}publicstaticintbbb{get{returnhttpcontext.current.request.cookies["key2"].toint(0);}}publicstaticdisplaysettingsccc{get{returnhttpcontext.current.request.cookies["key3"].fromjson
回到顶部
根据一些朋友提供的反馈,这里再补充4个需要注意的地方:
1.如果使用form登录验证且希望使用cookie方式时,建议设置cookieless="usecookies",因为这个参数的默认值是:cookieless="usedeviceprofile",asp.net可能会误判。dudu就吃过亏。
2.cookie有3个属性,一般我们可以不用设置,但它们的值可以在web.config中指定默认值:
3.虽然在写cookie时,我们可以设置name,value之外的其它属性,但是在读取时,是读不到这些设置的。其实在我的示例代码中有体现,我前面也忘记了说明了。
4.httprequest.cookies与httpresponse.cookies会有关系(很奇怪吧)。以下代码演示了这个现象:
protectedvoidpage_load(objectsender,eventargse){datetime.now.tostring().writecookie("t1",null);label1.text=showallcookies();guid.newguid().tostring().writecookie("t2",null);//如果去掉下面代码,将会看到2个t1response.cookies.remove("t1");response.cookies.remove("t2");}privatestringshowallcookies(){stringbuildersb=newstringbuilder();for(inti=0;i
上面的试验代码将会一直显示t1的cookie,这里就不再贴图了。