笔记整理--Http-Cookie

如何设置一个永远无法删除的Cookie -- 系统架构 -- IT技术博客大学习 -- 共学习 共进步! - Google Chrome (2013/6/20 9:46:38)

如何设置一个永远无法删除的Cookie

   在网站统计中,我们最常用的是用 Cookie标识身份,由于浏览器自带的 Cookie容易被用户删除。于是很多人使用 Flash Cookie来跟踪用户的信息。但是在目前360等软件帮助下,删除Flash Cookie也变得非常的简单。那么有没有什么方法让Cookie无法删除呢?答案是有的!做开发的基本上都理解灾备机制。即一台服务器如果出现了故障,则可由由另一台恢复回去。比如Cookie一旦删除后,这可通过Flash Cookies进行恢复。另外,除了Cookie和Flash Cookie外,到底还有哪些方式可以用来进行“用户识别”。

   1、标准的 Http Cookie

   HTTP Cookie是最常的用于“用户识别”的方式,以下为服务器与Cookie之间的交互流程:

  • 当我们用浏览器访问一个网站的时候,就会想服务器发起一个请求。

  • 根据请求的url和cookie本身的属性,筛选出需要发送给服务器的cookie(当然也有可能没有cookie)。

  • 如果有多个cookie的话,还会决定发送的顺序。

  • 把这些需要发送的cookie包含在HTTP的包头里发送给服务器。

  • 然后到了应答阶段,服务器会发回一个应答包头,包头里包含了cookie信息。

  • 浏览器解析这个cookie,包括名称,值,路径等元素。

  • 最后,把cookie保存在本地。

  •    至于哪些cookie会被发送到服务器端,是有一套规则的,例如域名选择、路径选择和Max-Age选择,这些都可以在RFC2109里找到。

       每次的http请求,cookie都会包含在包头里发送给服务器,这也是被开发者广为诟病的一个cookie缺点,因为这意味这每个请求都无形中增加了流量,特别是像请求图片这些资源的时候,附带的cookie信息是完全没有必要的。所以现在很多网站图片等静态资源都会用独立的域名运作,这样就可以单独对这些域名进行cookie设置。 除此以外,cookie还有以下影响比较大的缺点:

  • 安全性问题。cookie在http请求中是明文传递的,除非使用SSL通道,不然是不宜在cookie里放置重要信息。

  • 大小问题。每个cookie的大小不能超过4K。每个Domain下cookie个数根据浏览器不同也不同。

  •    关于Cookies的一些限制问题,可以参考下Nicholas的一篇文章: 浏览器允许的每个域名下的Cookie数:

  • IE7跟IE8限制为50个。

  • Firefox限制为50个。

  • Opera限制30个

  • Safari/WebKit没有限制,但是如果header的大小超过服务器能处理的情况下,则会出现错误。

  •    那如果Cookie数设置超过限制的时候,各浏览器又是如何处理呢:

  • Safari由于没有Cookie数的限制,所以不作讨论。

  • 在IE和Opera下,将会采用LRU(The Least Recently Used)方法,自动踢掉最老的cookie以腾出空间给新的cookie。IE和Opera使用这个方法。

  • Firefox就比较独特:它貌似会随机决定哪些cookie将会保留,尽管最后设置的cookie会被保留。所以在Firefox里不要超过cookie数的限制。

  •    cookie的总大小在各浏览器中也是不同的:

  • Firefox和Safari允许cookie多达4097个字节,其中4096个字节是名字和值,1个字节是=号。

  • Opera允许cookie多达4096个字节,这些字节包含名字、值和=号。

  • IE允许4095个字节,这些字节包含名字、值和=号。

  •    注意这里用的字节,也就是,如果是多字节字符,自然就会占用两个字节。在所有浏览器里,如果设置的cookie大小超过限制,那么它就会被忽略或者不被设置。

       从上面,我们可以看到,Cookie确实存在一些不足,但是它的一些缺点也正是它的优点,例如每个请求都会被放到包头里发送给服务器,正是这个特性我们才能很方便的传输sessionid。Cookie的出现可谓大大推动了网页的发展,而且在未来很长的一段时间里,Cookie还会继续发挥它的作用。但是也正是由于Cookie存在种种的不足,才会有新的本地存储技术出现的需求。

       2、Local Shared Objects (Flash Cookies)

       Local Shared Objects 即本地共享对象,长被称为Flash Cookie。Flash Cookies是由Adobe公司开发的一个技术,该技术允许Flash对象在每个域名上存储100KB的数据。LSO解决了Cookie的一些问题,例如大小,安全等。跟Cookie不同,LSO被保存为二进制文件(不过变量名具有可读性)。LSO具有了不少优点,但是缺点也是明显,就是它需要安装Flash这个插件。虽然现在Flash的普及率很高,但是这种依赖插件的技术始终不能解决问题的根源,而且为了使用这个方案不得不引入额外的swf和js文件。另外IE8开始和Chrome在删除历史记录的时候会将Flash Cookie删除掉。

       3、Silverlight Isolated Storage

       独立存储(Isolated Storage)是Silverlight 2中提供的一个客户端安全的存储,它是一个与Cookie机制类似的局部信任机制。独立存储机制的APIs 提供了一个虚拟的文件系统和可以访问这个虚拟文件系统的数据流对象。Silverlight中的独立存储是基于 .NET Framework中的独立存储来建立的,所以它仅仅是.NET Framework中独立存储的一个子集。

       Silverlight中的独立存储有以下一些特征:

  • 每个基于Silverlight的应用程序都被分配了属于它自己的一部分存储空间, 但是应用程序中的程序集却是在存储空间中共享的。一个应用程序被服务器赋给了一个唯一的固定的标识值。基于Silverlight的应用程序的虚拟文件系统现在就以一个标识值的方式来访问了。这个标识值必须是一个常量,这样每次应用程序运行时才可以找到这个共享的位置。

  • 独立存储的APIs 其实和其它的文件操作APIs类似,比如 File 和 Directory 这些用来访问和维护文件或文件夹的类。 它们都是基于FileStream APIs 来维护文件的内容的。

  • 独立存储严格的限制了应用程序可以存储的数据的大小,目前的上限是每个应用程序为1 MB。

  •    4、IE浏览器 userData 存储   (从IE9开始, 不再支持 userData)

       userData是微软在第一次浏览器大战中的产物,属于DHTML中的一种技术。相比起Cookie,userData在每个域名下可存储达的数据提升了不少,但是具体的大小视domain的安全域而定。userData的数据会一直存在,直到被删除或者到过期时间。并且基于安全的考虑,一个 userData 存储区只能用于同一目录和对同一协议进行存储。userData在数据的本地储存来说,比cookie进步了不少,但是它有个致命的缺点:仅支持IE。仅凭这一点,就注定了userData并不会有太大的作为,只能用作配合其他本地存储技术兼容低版本的IE。

       5、利用 HTTP ETags 存储Cookie

       Etag 是URL的Entity Tag,用于标示URL对象是否改变,区分不同语言和Session等等。具体内部含义是使服务器控制的,就像Cookie那样。

       HTTP协议规格说明定义ETag为“被请求变量的实体值” 。另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端。

       6、在浏览器历史记录中存储cookie

       大家都知道,用户访问过一次页面,就会存储在浏览器浏览历史里面,这个方法就是利用浏览器的这个特性。通过新建一个iframe去访问这个页面。如默认的url是http://www.example.com/test/。

       那他发送的路径会是加上了name跟value的。这里的name跟value分别是id跟123456,如

       http://www.example.com/test/id/123456

       发送方法为每个字母递增发送,并在最后加个”-“的结束符号

       http://www.example.com/test/i

       http://www.example.com/test/id

       …

       http://www.example.com/test/id/123456

       http://www.example.com/test/id/123456-

       那要相应的name value他是这样获取的。

       默认url加上 ”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=-“。其中一个字符,看看是否存在历史记录里面。不存在则循环查找下一个。如果这里查到i是访问过的 ,则继续循环,在i的后面循环检查。继而又查到d是访问过的。一直循环知道出现’-‘符号为止。继而解析获取到的字符串,那name value自然也就解析出来。

       但这样做的弊端很大。首先,必须要连续发送n个url,用户体验不好。获取的时候要遍历,也影响了浏览器的性能。所以不推荐。

       7、使用PNG像素图片保存Cookie

       以自动生成、强制缓存的PNG像素图片的RGB值形式保存cookie,使用HTML5 Canvas标签读取像素图片(cookie)。

       服务器创建一个宽100像素高1像素的黑色空白PNG(每个像素的RGB 颜色可存储3个字节,可存储600字节信息),然后将值拆分并按顺序每3个字母生成一个RGB颜色值并且按顺序设置到图片的像素点中,然后给这个图片设置一个expires非常长的时间(Expire 头,用于客户端缓存,不同于cookie的expire属性)读取的时候取出并且解析还原出来。要求浏览器必须支持html5才能用上此方法。ie8,ie9,ff,chrome,safari都是 ok的。

       8、使用window.name储存Cookie

       window.name 传输技术,原本是 Thomas Frank 用于解决 cookie 的一些劣势(每个域名 4 x 20 Kb 的限制、数据只能是字符串、设置和获取 cookie 语法的复杂等等)而发明的(详细见原文:《Session variables without cookies》),后来 Kris Zyp 在此方法的基础上强化了 window.name 传输 ,并引入到了 Dojo (dojox.io.windowName),用来解决跨域数据传输问题。

       window.name 的美妙之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

       name 在浏览器环境中是一个全局/window对象的属性,且当在 frame 中加载新页面时,name 的属性值依旧保持不变。通过在 iframe 中加载一个资源,该目标页面将设置 frame 的 name 属性。此 name 属性值可被获取到,以访问 Web 服务发送的信息。但 name 属性仅对相同域名的 frame 可访问。这意味着为了访问 name 属性,当远程 Web 服务页面被加载后,必须导航 frame 回到原始域。同源策略依旧防止其他 frame 访问 name 属性。一旦 name 属性获得,销毁 frame 。

       在最顶层,name 属性是不安全的,对于所有后续页面,设置在 name 属性中的任何信息都是可获得的。然而 windowName 模块总是在一个 iframe 中加载资源,并且一旦获取到数据,或者当你在最顶层浏览了一个新页面,这个 iframe 将被销毁,所以其他页面永远访问不到 window.name 属性。

       window.name 传输技术相比其他的跨域传输的一些优势:

  • 它是安全的。也就是说,它和其他的基于安全传输的 frame 一样安全,例如 Fragment Identifier messaging (FIM)和 Subspace。(I)Frames 也有他们自己的安全问题,由于 frame 可以改变其他 frame 的 location,但是这个是非常不同的安全溢出,通常不太严重。

  • 它比 FIM 更快,因为它不用处理小数据包大小的 Fragment Identifier ,并且它不会有更多的 IE 上的“机关枪”声音效果。它也比 Subspace 快,Subspace 需要加载两个 Iframe 和两个本地的 HTML 文件来处理一个请求。window.name 仅需要一个 Iframe 和一个本地文件。

  • 它比 FIM 和 Subspace 更简单和安全。FIM 稍微复杂,而 Subspace 非常复杂。Subspace 也有一些额外的限制和安装要求,如预先声明所有的目标主机和拥有针对若干不同特殊主机的 DNS 入口。window.name 非常简单和容易使用。

  • 它不需要任何插件(比如 Flash)或者替代技术(例如 Java)。

  •    9、使用HTML5客户端储存数据方法。

       HTML5 提供了四种在客户端存储数据的新方法,即

  • HTML5 Session Storage

  • HTML5 Local Storage

  • HTML5 Global Storage

  • HTML5 Database Storage via SQLite

  •    HTML5 Session Storage顾名思义它就如同Session。 针对一个 session 的数据存储,任何一个页面存储的信息在窗口中同一网站的任何页面都可以访问它存储的数据。每个窗口的值都是独立的,它的数据会因窗口的关闭而丢失,不同窗口间的sessionStorage是不可以共享的。

       HTML5 Local Storage 没有时间限制的数据存储,第二天、第二周或下一年之后,数据依然可用。也就是说,localStorage是永远不会过期的,除非主动删除数据。数据可跨越多个窗口,无视当前会话,被共同访问、使用。

       HTML5 Global Storage 同HTML5 Session Storage一样,区别在于其在浏览器关闭后,可以将存储的信息仍能够保留下来。目前只有FireFox支持,且只支持当前域下的存储。

       HTML5 Database Storage via SQLite (目前只谷歌浏览器支持):可以理解成一个Html5环境下可以用Js执行CRUD的Web数据库。对于简单的数据,使用sessionStorage和localStorage能够很好地完成存取,但是对于处理复杂的关系型数据,它就力不从心了。这也是 HTML 5 的“Web SQL Database”API 接口的应用所在。

       evercookie

       一些常见的用来标注用户身份的方法已经介绍过了,接下来要讲解的是如何使用。Evercookie是一个JavaScript API,通过它可以在浏览器中生成极其持久的cookie。它的目标就是在用户删除了传统cookie、Flash cookie(LSO)和其他缓存数据后,仍然可以识别客户端。Evercookie是通过利用浏览器不同的存储机制,把cookie数据保存在多个不同的地方实现的。此外,如果发现用户删除了其中一些cookie,Evercookie会利用这些机制重新创建它们。

       虽然Evercookie非常的强大,但是个人觉得大部分功能都比较花俏。如evercookie_png,evercookie_etag,evercookie_history,实际上这些操作均会对用户体验有一定影响。不过也体现了evercookie的宗旨:为了记录用户信息,无所不用其极。但针对于国内用户大部分为ie用户,而且很多网站是ie only,完全可把evercookie_userdata纳入考虑范围。同时一般情况下,装有silverlight插件的浏览器,几乎必然也装着flash插件。所以首选flash方案。

       Evercookie被认为是一项很邪恶的技术,事实上作者Kamkar的座右铭是“think bad, do good”。Kamkar说,他写evercookie是为了向用户展示公司可以跟踪他们的方法。

       “我希望evercookie只是向人们演示跟踪他们的是何种方法,由他们决定他们是否应该阻止这些方法,我作为一个安全业余爱好者,写evercookie也才用了不到一天的时间,因此我很容易想像那些受雇的开发者可以做出什么来。”

       有证据表明,Hulu、AOL和Spotify等网站已经开始在自己的网站上使用EverCookies。

       参考链接:

       http://en.wikipedia.org/wiki/HTTP_cookie

       http://en.wikipedia.org/wiki/Zombie_cookie

       http://samy.pl/evercookie/

       https://github.com/samyk/evercookie

浅析http协议、cookies和session机制、浏览器缓存 -- 网络系统 -- IT技术博客大学习 -- 共学习 共进步! - Google Chrome (2013/5/30 17:38:05)

浅析http协议、cookies和session机制、浏览器缓存

最近几天在复习http协议中headers,cookies、session、缓存等相关知识,发现些新知识点。

这篇文章注重结合PHP去理解这些内容,也就是比较注重实践部分。

http headers          

NO1对于web应用,用户群在客户端 (各种浏览器)点击任何一个连接向服务器发送http请求,这过程肯定需要3次握手,建立连接,服务器响应返回数据。

每次请求都有头部和实体部分,先看下面笔者监听QQ空间的headers,QQ空间的原因是它头部内容比较全

  1. Request Headers:  


  2. GET http://user.qzone.qq.com/445235728 HTTP/1.1  


  3. Host: user.qzone.qq.com  


  4. Connection: keep-alive  


  5. Cache-Control: max-age=0


  6. User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11  


  7. Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  


  8. Referer: http://qzone.qq.com/  


  9. Accept-Encoding:gzip,deflate,sdch  


  10. Accept-Language: zh-CN,zh;q=0.8  


  11. Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3  


  12. Cookie:o_cookie=445235728;(省略很多……)  


  13. If-Modified-Since: Wed, 13 Jun 2012 01:32:19 GMT  


  14. -----------------  


  15. Response Headers:  


  16. HTTP/1.1 200 OK  


  17. Connection:close  


  18. Server: QZHTTP-2.34.0  


  19. Date: Wed, 13 Jun 2012 02:59:31 GMT  


  20. Content-Encoding: gzip  


  21. Set-Cookie:login_time=61F0EEA02D704B1DBCF25166A74941B24F4BE24B205C466F;PATH=/;DOMAIN=qzone.qq.com  


  22. Set-Cookie:Loading=Yes;expires=Wed,13-Jun-201216:00:00GMT;PATH=/;DOMAIN=qzone.qq.com X-UA-Compatible: IE=EdgeLast-Modified: Wed, 13 Jun 2012 02:59:31 GMT  


  23. Cache-Control: max-age=0, no-transform  


  24. Content-Type: text/html;charset=utf-8  


  25. Transfer-Encoding: chunked  


客户端向服务端发请求headers和服务端响应客户端headers图:

通过图片可以看出:

1、客户端请求headers包含了请求行和一些头域。

请求行:请求的方法 统一资源标识器(URL)协议版本 ------这三者用空格分开,最后换行回车(\r\n) 例如:GET http://user.qzone.qq.com/445235728 HTTP/1.1

各种头域:这些头域都是有关键字和键值成对组合,最后换行回车(\r\n)结束,这些头域告诉服务器应该怎么去响应以及本身一些信息。

2、服务器响应

状态行:协议版本 响应状态 状态描述 ------这三者用空格分开,最后换行回车(\r\n) 例如:HTTP/1.1 200 OK

各种头域:这些头域也是有关键字和键值成对组合,最后换行回车(\r\n)结束,这些头域告诉客户端应该怎么去响应以及本身一些信息。

NO2

这里就不一一说每个头域的概念和作用,想了解的请看:http://www.phpben.com/?post=34现在介绍几个认为重要、在一些网站上的测试数据、以及请求返回各头域php代码实现

测试时间:2012.6.14前

测试对象:csdn 、cnbeta 、cnblos、腾讯(QQ空间、朋友网、新闻网)、新浪(微博、主页)、人人网、百度、淘宝、优酷、土豆这些网站

(1)Connection头域:这个头域只有http/1.1才有,默认是keep-alive值表示长连接,这样的话就不用每请求一个资源比如图片,css文件,js文件都要和服务器进行3此握手连接,这个在一定程度上弥补了http没状态的一个缺陷,减少连接服务器的时间。

查看测试网站Connection头域发现腾讯QQ空间、腾讯新闻网、新浪主页和微博,优酷和土豆Connection:close;除了这些其他的都是Connection:keep-alive

为什么?

1、connection: keep-alive 能正常使用的一个前提条件是还要提供content-length的头域告诉客户端正文的长度。那计算正文长度是个问题,对于多内容,集群服务器来说不是件易事。腾讯和新浪,优酷的这些都很难计算,对与工程师来说之间关闭了(默认是打开的)。

2、老服务器端不支持,对于腾讯,新浪这些老油条,服务器集群很庞大,难免有些老旧的不支持长连接的,为了一些兼容性问题,直接关闭了

Ps:这两点原因未求证过!^-^

用php headers(“Connection:keep-alive”);

(2)Content-Encoding头域

Content-Encoding文档的编码(Encode)方法.

上述网站出了cnbeta不用gzip压缩,优酷用deflate,其余都是。这也透漏一个重要信息,那就phper要掌握压缩gzip传输技术。

Php可以通过mod_gzip模块来实现。代码:ob_start("ob_gzhandler");

(3)Server头域暴漏服务器重要的安全信息。

Csdn:Server:nginx/0.7.68  ------------版本都暴露

腾讯QQ空间:Server:QZHTTP-2.34.0--------某位tx朋友透漏这是内部自己开发的服务器,这个可够安全

新浪微博:Server:apache -------------这个没暴漏版本

凤凰网:Server: nginx/0.8.53

人人网:Server:nginx/1.2.0

淘宝网:Tengine ---------这是淘宝内部技术团队开发的web服务器,基于Nginx

cnblogs博客园:Server:Microsoft-IIS/7.5

腾讯朋友网:Tencent/PWS ---------腾讯内部开发

腾讯新闻网:Server:squid/3.1.18

优酷网:Server: fswww1-----------是不是内部就不清楚,至少笔者不知道什么来的^_^

土豆网:Tengine/1.2.3

百度:server: BWS/1.0 ---------应该也是百度内部自己开发的服务器

很明显Server头域是返回服务器的信息,但也可以说暴漏信息,面对这个问题,大公司就自己开发基于自己功能的内部服务器。

(4)X-Powered-By头域可供修改,基于安全则可以修改

X-Powered-By头域反应什么语言什么版本执行后台程序。这个可以同个header函数修改

header("X-Powered-By:acb");

(5)Cache-control、expires、last-modified等重要头域

Cache-control:指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。

Php代码实现:header("cache-control: abc");abc是上述指令值一个或多个,多个用’,’分开

Expires:告诉浏览器指明应该在什么时候认为文档已经过期,从而不再缓存它。代码实现:header("Expires:". date('D, d M Y H:i:s \G\M\T', time()+10));--------这个是把时间截转化成格林时区字符串给expires头域,这个显示时间会比中国北京时间少8个小时,东8区的实现:header("Expires:". date('r', time()+10))

last-modified:这个是服务器返回给浏览器,浏览器下次请求则把该值赋给if-modified-since头域传给服务器,服务器就可以根据此值判断是否有改变,有则继续运行下去,否者返回304 not modified。Php设置expires头域一样。

代码:

  1. <?php

  2. if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < 10)) {  


  3.  header("HTTP/1.1 304 Not Modified");  


  4.     exit;  


  5.   }  


  6. header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );或者header("Last-Modified: " . date('r', time()) );  

  7. ?>

前者是格林时间格式,后者是中国时间。需要注意的就是php.ini 的时区prc则用后则,否者前者。笔者曾经试过在时区是prc的情况下用了前者,导致time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) <0永远成立,因为是负值。

注意:当请求页面有session_start()的时候,则不管是否有expires、cache-control、last-modified设置,则返回给客户端Cache-Control头域为Cache-Control:no-store, no-cache, must-revalidate Expires头域 Expires:Thu, 19 Nov 1981 08:52:00 GMT。这个问题烦了笔者2天,都以为php.ini 或是apache的问题。最后竟然是session_start()的问题。

二、             浏览器缓存动态

前面介绍了http headers几个告诉浏览器如何处理缓存。但不同浏览器处理各种头域的方式不同,以下就是笔者。

Ps:各个浏览器监听http headers的方法可以查看:http://www.phpben.com/?post=76

(1)header(“cache-control: no-store”)

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

地址栏回车

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

点击后退键

同上

同上

同上

同上

(2)header(“cache-control: no-cache”)

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

地址栏回车

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

点击后退键

同上

From cache

From cache

同上

(3)header(“cache-control:bublic”)

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

地址栏回车

from cache

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

点击后退键

From cache

From cache

From cache

同上

(4)header("cache-control:private"); header("cache-control: must-revalidate ")

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

地址栏回车

除第一次外都是from cache

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

点击后退键

From cache

From cache

From cache

同上

(5)header("cache-control:max-age=num");num是秒数

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

地址栏回车

秒数<num from cache

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

点击后退键

From cache

From cache

From cache

同上

(6)header("Expires:". date('D, d M Y H:i:s \G\M\T', time()+num)); num是秒数

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

地址栏回车

秒数<num from cache

重发请求,返回200状态

重发请求,返回200状态

重发请求,返回200状态

点击后退键

From cache

From cache

From cache

同上

(7)if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < num)) {

 header("HTTP/1.1 304 Not Modified");

    exit;

  } header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );

IE9

Google17.0

Firefox11

Maxthon3

点击刷新键

秒数<num 304 not modified

秒数<num 304 not modified

秒数<num 304 not modified

重发请求,返回200状态

地址栏回车

from cache

秒数<num 304 not modified

秒数<num 304 not modified

重发请求,返回200状态

点击后退键

From cache

From cache

From cache

同上

结论:

1、刷新对于任何浏览器且不管是什么cache-control,都会重新请求,一般返回是200,除非Last-Modified设置

2、后退键除非no-cache; no-store外都是使用缓存

3、Cache-control:no-store 在浏览器中任何操作都重新提交请求,包括后退

4、遨游3的缓存很差

5、IE9 的缓存很强,所以用ie9调试的时候尽可能点刷新而不是在地址栏回车

鉴于这种情况,对于不同的应用(有些要缓存,有些经常更新)对于不同的国家各种浏览器份额,而哪种缓存方式。中国IE比较多,加上360浏览器的加入(用IE内核),那就要主要参照IE浏览器。

但笔者还是比较喜欢header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );这种方式。结合起来connection:keep-alive能让缓存技术更成熟。

注意

1、也许你会问,用Cache-control:no-store或Cache-control:no-store,但调试页面还是没原来的缓存。然后清除浏览器缓存关掉重启浏览器, 缓存还在。这是因为你的web应用用了文件缓存如ecshop常出现这种情况,这种情况就要进web应用后台删除文件缓存。

2、调试的时候尽可能不要在地址栏回车,特别是IE,google还好一点,但是要知道这次的测试只是各个浏览器中的一个版本,所以调试的时候尽可能点刷新按钮。

3、但在cache-control:max-age=num 和expires 一起使用的时候,前者级别比较高,浏览器会忽略expires的设置。(上面没给出测试内容)

三、             Sessioncookies

Session 、cookies是程序员永远讨论的话题之一。

1、简单说一下cookiessession

(1)Cookies是保存在客户端的小段文本,随客户端点每一个请求发送该url下的所有cookies到服务器端。比如在谷歌浏览器下,打开ww.abc.com下的两个文件,a.php包含cookies1和cookies2,b.php包含了cookies3和cookies4,那么在a.php或b.php 点任意一个连接(当然是ww.abc.com服务器上的),浏览器就会把cookies1~4这4个cookies发送给服务器。但是如果在IE9有打开一个包含cookies5的c.php,哪门在google浏览器点击连接是不会发送cookies5的。

(2)Session则保存服务器段,通过唯一的值sessionID来区别每一个用户。SessionID随每个连接请求发送到服务器,服务器根据sessionID来识别客户端,再通过session 的key获取session值。SessionID传回服务器的实现方式可以通过cookies和url回写来实现。

注意

1、同一个浏览器打开同一个文件,如a.php ,或同时有设置session的两个文件a.php、b.php sessionID则只有一个。(时间上不能是打开a.php关闭浏览器再打开b.php)

2、不同浏览器在同一时间打开同意文件的sessionID也不一样

3、sessionID是服务器生成的不规则唯一字符串,如:

PHPSESSID=05dbfffd3453b7be02898fdca4fcd82b;------ PHPSESSID可以通过php.ini中session.name来改变,所以笔者在监听一些大型网站的时候查不出PHPSESSID,这是一个安全因素。

(3)cookies、session在php中的主要相关参数

(1)session.save_handler = ”files”

默认以文件方式存取session数据,如果想要使用自定义的处理器来存取session数据,比如数据库,用”user”。

(2)session.use_cookies = 1 前面说到sessionID用cookies来实现,这里就是,1表示用cookies

(3)session.use_trans_sid = 0 上面是用cookies来实现sessionID,这里值若是1则使用url回写方式,级别比session.use_cookies高

(4)session.use_only_cookies = 0 值为1则sessionID只可以用cookies实现,级别比前两个高

(5)session.cache_expire =180  session 缓存过期的秒数

(6)session.gc_maxlifetime = 1440

设定保存的session文件生存期,超过此参数设定秒数后,保存的数据将被视为’垃圾’并由垃圾回收程序清理。判断标准是最后访问数据的时间(对于FAT文件系统是最后刷新数据的时间)。如果多个脚本共享同一个session.save_path目录但session.gc_maxlifetime不同,将以所有session.gc_maxlifetime指令中的最小值为准。

(4)图说cookie 、ssession

php代码如下

  1. session_start();  


  2. $_SESSION['favcolor'] = 'green';  


  3. $_SESSION['animal']   = 'cat';  


  4. $_SESSION['time']     = time();  


  5. setcookie("cookie1","www.phpben.com",time()+3600*10);  


  6. setcookie("cookie2","www.phpben.com",time()+3600*10);  

图片:

结论:

1、 第一次请求是没用cookies的,而第二次有PHPSESSID和两个cookies是因为服务器第一请求返回这个三个cookies。

2、第二次请求比第一次多返回PHPSESSID这个cookies,在第二次则没有了,直到session过期后重新设置。

2、;浏览器关掉cookiessession是否可以正常运行?

前面提及sessionID的时候有两种方式。

(1)cookies 方式,在session.use_trans_sid=0 and session.use_cookies = 1的情况下使用。这种方法是每次浏览器端点每个请求,都把sessionID发送到服务器。

(2)url回写,session.use_only_cookies = 0 and session.use_trans_sid=1的情况下,服务器会忽略session.use_trans_sid,在浏览器发hhtp请求后,服务器会在返回页面内容中每个连接后面加上PHPSESSID=05dbfffd3453b7be02898fdca4fcd82b (在php.ini没改session.name,默认是PHPSESSID),这样就算客户端的浏览器禁止了cookies,一样能实现session功能。

这里来个测试:

在1.php文件代码:

  1. <?php  


  2. echo 'Welcome to page #1<br/>';  


  3. session_start();  


  4. $_SESSION['favcolor'] = 'green';  


  5. $_SESSION['animal']   = 'cat';  


  6. $_SESSION['time']     = time();  




  7. // Works if session cookie was accepted


  8. echo '<br /><a href="2.php">page 1 (这个href中没SID参数)</a><br/>';  




  9. // Or maybe pass along the session id, if needed


  10. echo '<br /><a href="2.php?' . SID . '">page 2 (这个href中有SID参数)</a><br/>';  


  11. ?>  


在2.php文件代码:

  1. <?php  


  2. session_start();  




  3. echo 'Welcome to page #2<br />';  




  4. echo $_SESSION['favcolor'],'<br/>'; // green


  5. echo $_SESSION['animal'],'<br/>';   // cat


  6. echo date('Y m d H:i:s', $_SESSION['time']),'<br/>';  




  7. // You may want to use SID here, like we did in page1.php


  8. echo '<br /><a href="1.php">return page 1</a>';  


  9. ?>  

情景1:没禁用浏览器的cookies(用cookies实现session),则在2.php能正常输出

情景2:禁用用浏览器的cookies且在php.ini开启session.use_trans_sid=1,通过1.php第一连接过去显示不了session的值,但第二个连接则正常显示。(说明url回写正常运行)

参考:http://blog.csdn.net/heiyeshuwu/article/details/7604538

写一个简单的爬虫 - 如何模拟登录网站 - 从波 - 博客园 - Google Chrome (2013/5/16 10:16:41)

写一个简单的爬虫 - 如何模拟登录网站

清除浏览器保存的 cookie

chrome 是这个地址:chrome://chrome/settings/cookies,搜索如163,然后remove all

firefox 是 Edit -> Preferences -> Privacy -> remove individual cookies

(或许还需要关闭已经登录的网页)

 

查看完整的登录过程

chrome 的 Developer Tools 由于会随着 url 的跳转发生改变,不能查看到完整的登录过程

firefox 的 httpfox 插件可以捕捉到完整的登录过程

 

以163的登录过程为例:

chrome 捕捉到的依次为:next.jsp, main.jsp, master.css等

 

firefox 捕捉到的依次为:logins.jsp, next.jsp, main.jsp, master.css等

chrome 的 Developer Tools 漏掉了一个最关键的 login.jsp

因此模拟网站的登录过程,以 firefox 为主,完整捕捉登录过程;用chrome 辅助查看分析,可以很方便地看到整体的情况,也很方便复制。

tips:如果看到Request Headers里带有Cookie,说明前面还有一步获取Cookie的过程。

 

分析登录过程

第一步的 https://reg.163.com/logins.jsp,post 的数据有三个,其中有username, password,都是明文的。

试试在 url 后面加上 post 的数据看看会发生什么,https://reg.163.com/logins.jsp?username=abcdefg@163.com&password=1234567,将username, password 换为自己的,登录成功。

1、可以用post方法带上post的数据访问url;

2、也可以用get方法直接访问加了post数据的url;

3、还可以直接复制 chrome 的 Developer Tools 中最终跳转的页面的 headers,带那个headers访问该页面,如:

 

登录成功后,保存返回的cookie,后续操作带着该cookie访问即可。

HTTP代理如何正确处理Cookie【转】_zboole_新浪博客 - Google Chrome (2013/5/6 14:38:10)

HTTP代理如何正确处理Cookie【转】

 (2011-02-12 15:43:42)
标签: 

杂谈

分类: 协议分析
大多数的 Web 应用程序都要求维护某种会话状态,如用户购物车的内容。这种会话状态的保持很多情况下需要借助于Cookie或者Session的帮助。本文结合在线页面 翻译 (Machine Translation System)项目中对于Cookie的处理方法,探讨一下如何在HTTP应用代理中正确处理Cookie的传递和管理问题。

读者定位为具有Java和Web开发经验的开发和设计人员。

读者可以学习到关于Cookie的工作原理和Cookie协议的细节,以及在一个HTTP应用代理的场景下Cookie的管理和处理思想,并可以直接使用文中的代码和思路,提高工作效率。

随着越来越多的系统移植到了Web上,HTTP协议具有了比以前更广泛的应用。不同的系统对WEB实现提出了不同的要求,基于HTTP协议的网络应 用正趋于复杂化和多元化。很多应用需要把用户请求的页面进行处理后再返回给用户,比如页面关键字过滤,页面内容缓存、内容搜索、页面翻译等等。这些应用在 实际效果上类似于一个HTTP应用代理:它们首先接受用户的请求,根据用户请求的URL去真正的目标服务器取回目标页面,再根据不同应用的要求做出相应处 理后返回给用户。这样用户直接面对的就是这个HTTP应用代理,而通过它与其他页面进行交互。Cookie或Session技术的应用,解决了HTTP协 议的一个问题 -- 无法保持客户状态,因此它现在被广泛应用于各种Web站点中。上面提到的那些应用如果不能处理好Cookie和Session的传递、更新和废除等问题, 就会极大的限制它们所能处理站点的范围,因此如何在HTTP应用代理中正确处理Cookie,成为一个必须解决的问题。本文结合在页面翻译 (Machine Translation System)项目中对于Cookie的处理方法,探讨一下这方面的解决方案。

MTS项目简介及讨论前提

Machine Translation System(以下简称MTS)是一个在线实时页面翻译系统,为用户在线提供把英文页面翻译成其他9种语言的服务。用户通过向MTS系统提交一个类似下面 的URL使用此服务,其中参数url指明了用户所需要翻译的目标地址,参数language指明了所需翻译成的目标语言,www.mts.com是假想中 提供MTS服务的站点。

HTTP://www.mts.com/translate?url=http://www.ibm.com/&language=French

一个完整的MTS系统处理过程可以分解成以下几个步骤:

  • 用户向MTS提交合适的URL。
  • MTS在接到用户的请求后,解析出用户需要翻译的目标地址和目标语言,根据用户请求的目标地址,把请求转发到目标服务器。
  • MTS接受来自目标服务器的应答,包括页面信息和HTTP头信息。
  • MTS在确定得到正确的目标页面后,把页面内容送入WebSphere Translation Server进行翻译。
  • 把翻译后的页面连同修改后的HTTP头信息提交给用户。

MTS逻辑图(略)


当然,这其中涉及到很多的应用处理。比如与各种HTTP/HTTPS站点建立联结、根据HTTP头信息进行页面跳转和错误处理、为始终保持用户在翻 译模式下而对目标的HTML页面进行分析和修改,根据系统设置对某些DNT(Do Not Translate)的页面进行过滤和跳转,当然还有对Cookie的处理等等。其他问题跟这篇文章关联不大,我们重点讨论在这种情况下的Cookie处 理。Cookie跟随目标服务器的HTTP头信息被MTS接收到,经过MTS整理之后发给客户端浏览器。MTS在接到下一次用户对同一个站点的翻译请求 时,再把从客户端得到的Cookie发送给目标服务器。

在以上的场景中,MTS充当的作用类似于一种HTTP应用代理服务器,它代替用户取得目标页面,并在作出相应处理后再提交给用户。当然,这种代理服 务器不需要用户修改浏览器的代理服务器参数或者网络配置,而只是简单的在浏览器的地址栏中输入一个MTS能够识别的URL即可。此篇文章也是在这样一个应 用场景的基础上,展开对HTTP应用代理服务器如何处理Cookie的讨论。

问题的产生

在MTS系统中,目标服务器的Cookie在两个地方会产生问题。当MTS接收目标服务器应答的时候,Cookie随着HTTP头信息被MTS接收 到的。这时候目标服务器认为MTS就是最终客户,因此它赋予了Cookie与目标服务器相符的属性。而如果MTS把这些Cookie原封不动的保存在 HTTP头信息中,传给真正的最终用户的话,用户的浏览器会因为这些Cookie不合法而忽略它们。同理,当Cookie从浏览器端传回目标服务器的时 候,也会遇到相同的问题。因此有必要对Cookie进行一些处理,以保证用户的浏览器能真正识别和利用这些Cookie。

但是为何用户浏览器无法识别从目标服务器传过来的原始Cookie呢?这是因为出于安全性的考虑,Cookie规范制定的时候对Cookie的产生 和接受设置了一些严格的规范,不符合这些规范的Cookie,浏览器和服务器都将予以忽略。下面我们从Cookie规范入手进行介绍。

Cookie的规范介绍

目前有以下几种Cookie规范:

  • Netscape cookie草案:是最早的cookie规范,基于rfc2109。尽管这个规范与rc2109有较大的差别,但是很多服务器都与之兼容。
  • rfc2109, 是w3c发布的第一个官方cookie规范。理论上讲,所有的服务器在处理cookie(版本1)时,都要遵循此规范。遗憾的是,这个规范太严格了,以致很多服务器不正确的实施了该规范或仍在使用Netscape规范。
  • rfc2965规范定义了cookie版本2,并说明了cookie版本1的不足。

rfc2965规范的使用,目前并不多。rfc2109规范相应要严格得多,在实际应用上,并不是所有的浏览器和Web服务器都严格遵守。因此相比 较而言,Netscape cookie草案倒是一个比较简洁和被广泛支持的Cookie规范,因此我们在这里以Netscape cookie草案为基础进行讨论,对于其他两种规范,我们的讨论和代码具有相同的意义。关于Netscape cookie草案的细节,大家可以参照Netscape官方站点,这里我们列举一些和我们讨论有关的内容。

根据Netscape cookie草案的描述,Cookie 是Web 服务器向用户的浏览器发送的一段ASCII码文本。一旦收到Cookie,浏览器会把Cookie的信息片断以"名/值"对(name-value pairs)的形式储存保存在本地。这以后,每当向同一个Web 服务器请求一个新的文档时,Web 浏览器都会发送之站点以前存储在本地的Cookie。创建Cookie的最初目的是想让Web服务器能够通过多个HTTP请求追踪客户。有些复杂的网络应 用需要在不同的网页之间保持一致,它们需要这种会话状态的保持能力。

浏览器与Web服务器通过HTTP协议进行通讯,而Cookie就是保存在HTTP协议的请求或者应答头部(在HTTP协议中,数据包括两部分,一 部分是头部,由一些名值对构成,用来描述要被传输数据的一些信息。一部分是主体(body),是真正的数据(如HTML页面等))进行传送的。

在HTML文档被发送之前,Web服务器通过传送HTTP 包头中的Set-Cookie 消息把一个cookie 发送到用户的浏览器中。下面是一个遵循Netscape cookie草案的完整的Set-Cookie 头:


Set-Cookie:customer=huangxp; path=/foo; domain=.ibm.com; 
expires= Wednesday, 19-OCT-05 23:12:40 GMT; [secure]

Set-Cookie的每个属性解释如下:

  • Customer=huangxp 一个"名称=值"对,把名称customer设置为值"huangxp",这个属性在Cookie中必须有。
  • path=/foo 控制哪些访问能够触发cookie 的发送。如果没有指定path,cookie 会在所有对此站点的HTTP 传送时发送。如果path=/directory,只有访问/directory 下面的网页时,cookie才被发送。在这个例子中,用户在访问目录/foo下的内容时,浏览器将发送此cookie。如果指定了path,但是path 与当前访问的url不符,则此cookie将被忽略。
  • domain=.ibm.com 指定cookie被发送到哪台计算机上。正常情况下,cookie只被送回最初向用户发送cookie 的计算机。在这个例子中,cookie 会被发送到任何在.ibm.com域中的主机。如果domain 被设为空,domain 就被设置为和提供cookie 的Web 服务器相同。如果domain不为空,并且它的值又和提供cookie的Web服务器域名不符,这个Cookie将被忽略。
  • expires= Wednesday, 19-OCT-05 23:12:40 GMT 指定cookie 失效的时间。如果没有指定失效时间,这个cookie 就不会被写入计算机的硬盘上,并且只持续到这次会话结束。
  • secure 如果secure 这个词被作为Set-Cookie 头的一部分,那么cookie 只能通过安全通道传输(目前即SSL通道)。否则,浏览器将忽略此Cookie。
 
 
Cookie有一个Expires(有效期)属性,这个属性决定了Cookie的保存时间,服务器可以通过设定Expires字段的数值,来改变Cookie的保存时间。如果不设置该属性,那么Cookie只在浏览网页期间有效,关闭浏览器,这些Cookie自动消失,绝大多数网站属于这种情况。通常情况下,Cookie包含Server、Expires、Name、value这几个字段,其中对服务器有用的只是Name和value字段,Expires等字段的内容仅仅是为了告诉浏览器如何处理这些Cookies。
 

一旦浏览器接收了cookie,这个cookie和对远端Web服务器的连续请求将一起被浏览器发送。例如 前一个cookie 被存入浏览器并且浏览器试图请求 URL http://www.ibm.com/foo/index.html 时,下面的HTTP 包头就被发送到远端的Web服务器。

GET /foo/index.html HTTP/1.0
Cookie:customer=huangxp


一次典型的网络浏览过程

在了解了Cookie协议的一些基本内容之后,让我们看看一次典型的网络浏览过程中浏览器如何识别和处理Cookie:

  • 浏览器对于Web服务器应答包头中Cookie的操作步骤:
    1. 从Web服务器的应答包头中提取所有的cookie。
    2. 解析这些cookie的组成部分(名称,值,路径等等)。
    3. 判定主机是否允许设置这些cookie。允许的话,则把这些Cookie存储在本地。
  • 浏览器对Web服务器请求包头中所有的Cookie进行筛选的步骤:
    1. 根据请求的URL和本地存储cookie的属性,判断那些Cookie能被发送给Web服务器。
    2. 对于多个cookie,判定发送的顺序。
    3. 把需要发送的Cookie加入到请求HTTP包头中一起发送。

由MTS代理的网络浏览过程

以上我们了解了在一个典型的浏览器与Web服务器交互的时候,Cookie的传递过程。下面我们将看到,如果在MTS代理网络浏览的过程中,不对Cookie进行修改,上面的Cookie传递过程将无法实现。

1. 假设用户希望把 http://www.ibm.com/foo/index.html 页面翻译成法文,应该使用如下的url对MTS发出请求

http://www.mts.com/translate?url=http://www.ibm.com/foo/index.html&language=French

2. MTS接收用户的请求,连接远程目标服务器 http://www.ibm.com/foo/index.html。目标服务器做出应答,返回HTTP头和HTML页面内容。其中,典型的HTTP头内容如下:


HTTP/1.1 200 OK
Date: Mon, 24 Oct 2005 06:54:41 GMT
Server: IBM_HTTP_Server
Cache-Control: no-cache
Content-Length: 19885
Connection: close
Set-Cookie:customer=huangxp; path=/foo; domain=.ibm.com; 
expires= Wednesday, 19-OCT-05 23:12:40 GMT
Content-Type: text/html

3. MTS不对Set-Cookie后的内容作任何处理,直接把它加到用户浏览器的应答头上发送给浏览器。

4. 浏览器将从Set-Cookie中解析出domain和path的值,分别是.ibm.com和/foo,并与请求的url:http: //www.mts.com/translate?url=http://www.ibm.com/foo/index.html&language =French进行比较。请求url的domain是www.mts.com,path是/,与Set-Cookie中的属性不符,所以浏览器将忽略此 Cookie。

另外,在浏览器发送Cookie的时候也会遇到同样的问题,同样如上例,如果浏览器里本来已经存储了http: //www.ibm.com/foo/的Cookie,但由于用户要通过MTS访问此站点,浏览器经不会把已经存储的Cookie上转到MTS中,MTS 也就无法把之传递到http://ibm.com/foo/上。

基于上面Cookie规范的介绍和例证,我们能看出,浏览器在接受某一个站点的Cookie的时候,需要检查Cookie的参数domain、 path、secure,看是否与当前的站点和URL相符,如果不符的话,就会忽略。另一方面。浏览器在上传Cookie的时候,也会根据当前所访问站点 的属性,上传相关的Cookie,而其他的Cookie则不予上传。

至此,我们讨论了需要修改Cookie的根本原因在于Cookie规范的限制。下面我们讨论两种解决问题的思路。

解决问题的两种思路

Cookie的存在是要解决HTTP协议本身先天的缺陷-无状态性,它为用户保存了一些需要的状态信息。因此我们解决此问题的最本质的出发点,也就是找到一种途径能为用户保存Cookie所提供用户状态信息,实际上就是Name/Value对。

思路一

第一种思路就是修改目标服务器取得的Cookie,使之符合MTS站点的属性,然后作为MTS站点的Cookie存储到用户的浏览器中去。当然,这 种修改必须保留原始Cookie的所有属性值,当以后访问同一个目标服务器的时候,MTS能根据保存的属性值还原出原始Cookie,然后进行提交。

具体到属性值的保存位置,没有太多选择的余地,实际上,domain,path,secure,expires这几个属性都无法利用,只有利用 name=value这一属性对。我们的做法是创造一个新的Cookie,把原始Cookie的domain,path的值与name值进行编码,用分隔 符附加在Name值的后面,符值给新的Cookie。这样做也同时避免了不同目标服务器如果出现同名的Cookie,将会互相覆盖的情况(Cookie规 范里面也规定了,客户端以domain,path,name作为Cookie的唯一标示)。而原始Cookie的secure和expires值,直接符 给新的Cookie,新Cookie的domain和path设成缺省值,这样,新Cookie就可以被浏览器正常接受。由于浏览器接受的所有 Cookie的domain和path值都一样,因此每次用户对MTS提出请求时,浏览器都会把所有与MTS站点相关的Cookie上传,因此,MTS还 需要还原原始的Cookie,过滤掉与目标服务器不相干的Cookie,然后上传有用的Cookie。

这种思路的优点在于Cookie存储在客户端,可以做到长期存储,浏览器自己根据Cookie的expires值做出判断,省掉很多开发的麻烦。缺 点是转换的过程相对较复杂。另外还有一个缺点,也是由于Cookie规范的限制所造成的。Cookie规范对于一个浏览器同时能够存储的Cookie数量 作出了规定。

  • 总共300 个cookie
  • 每个Cookie 4 K 的存储容量
  • 每一个domain 或者 server 20 个cookie。

以上是浏览器所应达到的最小存储数量,超出这个限制,浏览器应该自动按照最少最近被使用的原则删除超出得Cookie。由于用户有可能通过MTS这 一个网站翻译大量的目标服务器,因此浏览器存储在MTS的domain下的cookie数量就很有可能超过20个,这时候就会导致某些Cookie被删 除。一般这也不会造成太大问题,因为规范是要求浏览器删除最少最近被使用的Cookie,但我们在实际测试当中发现有些浏览器并不遵守这样的规范,而是删 除最新的Cookie,这就将导致用户很大的不便。

思路二

第二种思路在于把原始的Cookie组织成dataBean,存储到用户的Session当中去。这样,在用户端只需要存储一个SessionID 的Cookie,而不需要存储所有目标服务器的每一个Cookie。另外,当接收到用户的又一次翻译请求时,再从Session当中取出所有的 dataBean,逐一进行分析,找出与用户所请求的目标服务器相符的原始Cookie,进行提交。

这种思路可以克服上一种思路中Cookie超过标准数量时的缺陷,而且不需编码保存原始的Cookie属性值,减少了程序的复杂度。缺点是需要程序 员自己处理expires。而且由于是把Cookie存储在Session中,一旦Session失效,所有Cookie都将被删除,所以,无法保存那些 长期的Cookie。

总之,两种思路各有利弊,在实际应用当中要权衡考虑。下面我们针对两种思路进行技术实现,分别对应方案一和方案二。

由于MTS需要与目标服务器连接,遵循HTTP协议读取和返回Cookie,但是如果用JDK中的java.net.URLConnection处理Cookie将非常不方便,因此我们使用HTTPClient来处理与目标服务器的连接。

方案一:Cookie存储在浏览器端

用户每发起一次新的请求,浏览器在检查完本地存储Cookie的有效性后,会把所有由MTS产生的有效Cookie附加在请求头里送到MTS。 MTS接受到客户端的翻译请求后,从Request中提取出所有的Cookie,还原后根据目标服务器的domain和path进行过滤。产生所有与目标 服务器相关的Cookie。


//从request中获取所有的Cookie
javax.servlet.http.Cookie[] theCookies = request.getCookies();
ArrayList cookiesList = new ArrayList();
String url = request.getParameter("url");
String domain = URLUtil.getURLHost(url);
String path = URLUtil.getPath(url);
if (theCookies != null)
{
for (int i = 0; i < theCookies.length; i++)
{
RE r = new RE();
//用正则表达式把name项还原成domain,path,name
REDebugCompiler compiler = new REDebugCompiler();
r.setProgram(compiler.compile("\\|\\|"));
String[] values = r.split(theCookies[i].getName());
//"9.181.116.183||/MTModule||testCookie:value1" or " || 
||testCookie:value1"
if (values.length == 3)
{
if (values[0].trim().startsWith("."))
{
if (!domain.endsWith(values[0].trim()))
continue;
} else if (!domain.endsWith("://" + values[0].trim()))
continue;
if (!path.startsWith(values[1].trim()))
continue;
Cookie tempCookie = new Cookie();
tempCookie.setDomain(
("".equals(values[0].trim())) ? null : values[0]);
tempCookie.setPath(
("".equals(values[1].trim())) ? null : values[1]);
tempCookie.setName(
("".equals(values[2].trim())) ? null : values[2]);
tempCookie.setSecure(theCookies[i].getSecure());
tempCookie.setValue(theCookies[i].getValue());
tempCookie.setVersion(theCookies[i].getVersion());
tempCookie.setComment(theCookies[i].getComment());
cookiesList.add(tempCookie);
}
}
}
//transferedCookie用来存储将被传到目标服务器的Cookie
Cookie[] transferedCookie = new Cookie[cookiesList.size()];
cookiesList.toArray(transferedCookie);

接下来,需要把Cookie送到目标服务器中。我们使用HTTPClient与目标服务器连接。HTTPClient在与目标服务器连接以后,允许 服务器设置Cookie并在需要的时候自动将Cookie返回服务器,也支持手工设置 Cookie后发送到服务器端。但是,由于如何处理cookie有几个规范互相冲突:Netscape Cookie 草案、RFC2109、RFC2965,而且还有很大数量的软件商的Cookie实现不遵循任何规范。 为了处理这种状况,需要把HttpClient设置成Cookie兼容模式,这样可以最大限度的处理好各种Cookie。下面的代码把Cookie送到目 标服务器。


HttpClient client = new HttpClient();
//从request得到所有需要传输的cookie 
Cookie[] questCookie = getCookieFromRequest(request);
//设置HTTPClient为Cookie兼容模式
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
if (questCookie.length > 0)
//把Cookie加到httpclient中
client.getState().addCookies(questCookie);
HttpMethod method = new GetMethod(TagerURL);
//向目标服务器发送请求
int statusCode = client.executeMethod(method);
method.releaseConnection();

MTS把请求和Cookie送出后,继续接收目标服务器的应答,读取返回的原始Cookie,并转换成可以存储在用户浏览器端的Cookie。下面 的代码将对原始Cookie的内容进行变换,保留expires和secure等项,把domain和path项编码到name中去。


//从HTTPClient中取得所有的Cookie
Cookie[] temp = client.getState().getCookies();
if (temp != null)
{
javax.servlet.httpCookie theCookie = new javax.servlet.http.Cookie[temp.length];
//逐一对Cookie进行处理
for (int i = 0; i < temp.length; i++)
{ StringBuffer sb = new StringBuffer();
//编码成domain||path||name
sb.append(
temp[i].getDomain() == null ? " " : temp[i].getDomain());
sb.append("||");
sb.append(temp[i].getPath() == null ? " " : temp[i].getPath());
sb.append("||");
sb.append(temp[i].getName() == null ? " " : temp[i].getName());
theCookie[i] =
new Cookie(sb.toString(),temp[i].getValue());
//复制其他项
theCookie[i].setMaxAge(theCookie[i].getMaxAge();
theCookie[i].setSecure(temp[i].getSecure());
theCookie[i].setVersion(temp[i].getVersion());
theCookie[i].setComment(temp[i].getComment());
}
}

最后一步,把这些Cookie保存到response里,随HTTP应答头返回用户浏览器。并保存在浏览器中。


//把所有转换后的Cookie加入response
for (int i = 0; i < theCookie.length; i++) {
response.addCookie(theCookie[i]);
}

至此,我们已经完成了接收用户请求,转换Cookie,发送到目标服务器,接收目标服务器的原始Cookie,并保存在客户浏览器的整个处理过程。


方案二:Cookie存储在服务器端

在此种方案中,目标服务器返回给MTS的Cookie将被组织成dataBean,存储在用户的Session中。因此,我们首先生成一个用来存储 Cookie的类CookiesBean,根据它的特性,它可以继承ArraryList类。此对象将存储用户访问目标服务器时接收到的所有 Cookie,并提供与新接收到的Cookie融合的功能,同时能够删除过期的Cookie,更新同名的Cookie。


public class CookiesBean extends ArrayList
{

public CookiesBean(Cookie[] cook)
{
if (cook == null)
return;
//add all cookie which isn't expired.
for (int i = 0; i < cook.length; i++)
{
if (!cook[i].isExpired())
{
add(cook[i]);
}
}
}

public void RefreshBean(CookiesBean bean)
{
if (bean == null)
return;
Iterator it = bean.iterator();
//针对bean中的每一个Cookie进行处理
while (it.hasNext())
{
Cookie beanCookie = (Cookie) it.next();
if (beanCookie == null) continue;
ArrayList drop = new ArrayList();
Iterator thisIt = iterator();
//取出存储的Cookie进行比较和处理
while (thisIt.hasNext())
{
Cookie thisCookie = (Cookie) thisIt.next();
if (thisCookie == null) continue;
//比较name,domain和path,如果一样的话,则把此Cookie移到drop中
if (CommonMethods
.CompString(beanCookie.getName(), thisCookie.getName())
&& CommonMethods.CompString(
beanCookie.getDomain(),
thisCookie.getDomain())
&& CommonMethods.CompString(
beanCookie.getPath(),
thisCookie.getPath()))
{
drop.add(thisCookie);
continue;
}
//删除过期的Cookie
if (thisCookie.isExpired())
drop.add(thisCookie);
}
//删除所有drop中的Cookie
this.removeAll(drop);
//如果beanCookie有效,则加入到存储区中。
if (!beanCookie.isExpired())
add(beanCookie);
}
return;
}
}

当MTS接受到客户端的翻译请求后,会从Session中提取出所有的dataBean,并得到存储的所有Cookie。如以下代码:


CookiesBean dataBean = null;
Cookie[] theCookies = new Cookie[0];
ArrayList cookiesList = new ArrayList();
//获得Session,并获得dataBean
HttpSession session = request.getSession(false);
if (session != null)
{
dataBean = (CookiesBean) session.getAttribute(SESSION_NAME);
}
else
{
return theCookies;
}

MTS在所有的存储的Cookie中,检查Cookie的Domain、path和secure的值,筛选出符合目标服务器的Cookie。


//提取目标服务器的domain和path
String url = context.getURL();
String domain = URLUtil.getURLHost(url);
String path = url.substring(domain.length());

String cookiedomain = null;
String cookiepath = null;
//逐个比较Cookie的domain和path
//把符合要求的Cookie纪录到cookiesList中
for (int i = 0; i < dataBean.size(); i++)
{
Cookie cookie = (Cookie) dataBean.get(i);
if (cookie == null) continue;
cookiedomain =
(cookie.getDomain() == null) ? "" : cookie.getDomain();
cookiepath = (cookie.getPath() == null) ? " " : cookie.getPath();
if (!path.startsWith(cookiepath))
continue;
if (cookiedomain.startsWith("."))
{
if (!domain.endsWith(cookiedomain))
continue;
}
else if (!domain.endsWith("://" + cookiedomain))
continue;
if (cookie.isExpired())
continue;
if (cookie.getSecure() && url.toLowerCase().startsWith("http:"))
continue;
cookiesList.add(cookie);
}
theCookies = new Cookie[cookiesList.size()];
cookiesList.toArray(theCookies);
return theCookies;

把Cookie送到目标服务器的代码与方案一基本一样,在此忽略。

最后一步,需要把Cookie存储到Session中。下面的代码将从目标服务器接受Cookie,融入到dataBean中,并保存到客户的Session中。


//从目标服务器得到Cookie集
Cookie[] cookies = client.getState().getCookies();
CookiesBean bean = new CookiesBean(cookies);
CookiesBean dataBean = bean;
//取得用户Session
HttpSession session = request.getSession(false);
if (session != null)
{
if (session.getAttribute(SESSION_NAME) != null)
{
//读取Session中存取的dataBean
dataBean = (CookiesBean) session.getAttribute(SESSION_NAME);
//目标服务器端的Cookie融合到Session中的dataBean中
dataBean.RefreshBean(bean);
}
//把最终的dataBean存入Session中
session.setAttribute(SESSION_NAME, dataBean);
}

至此,我们已经完成了在Session中保存个目标服务器所产生Cookie的整个处理过程。

关于Session的考虑

在研究完如何管理和传递Cookie之后,我们也需要研究一下Session的传递。因为目前大部分站点都在采用Session机制保存用户状态数据,如果不能解决Session的传递问题,HTTP应用代理服务器的适用范围同样会大打折扣。

首先我们了解一下Session的实现机制。Session是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息。当程序需要为某个 客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串。

保存这个session id的方式之一就是采用Cookie。一般这个Cookie的名字都类似于 SESSIONID。比如WebSphere对于Web应用程序生成的Cookie:JSESSIONID= 0001HWF4iVD94pY8Cpbx6U4CXkf:10lro0398,它的名字就是 JSESSIONID。

保存session id的其他方式还包括URL重写和表单隐藏字段。这两种方式都不需要代理服务器作特殊处理。因此实际上,我们解决了Cookie的管理和传递的问题之后,也就解决了Session的管理和传递。



结束语

从上面的讨论中可以看出,由于Cookie本身的规范限制,HTTP应用代理所必需面对的一个问题就是如何对Cookie进行正确的处理。本文对此 提出了两种解决思路并列出了实现代码。对于MTS项目本身,我们使用的是第二种方案。开发人员在认识好Cookie本身的特性之后,参照本文的思路,根据 自己系统的特点,也会找出更适宜的解决方案。

转自:http://blog.csdn.net/joliny/archive/2008/11/23/3355239.aspx

原文(繁体):http://www.ibm.com/developerworks/tw/library/j-cookie/

 

参考资料

  • Netscape Cookie Specification 对Netscape Cookie 使用的特性进行了简要的介绍。

  • RFC2965:HTTP State Management Mechanism 介绍了HTTP 状态管理机制

  • RFC2109 w3c发布的第一个官方cookie规范

  • RFC2616:Hypertext Transfer Protocol 超文字传输协定

  • Ronald Tschalr开发了HTTPClient,将其作为URLConnection 的替代品。

  • Jakarta Regexp Apache 的开源专案,处理正则运算式的java组。

 

细说Cookie - Fish Li - 博客园 - Google Chrome (2013/4/18 11:06:27)

Cookie虽然是个很简单的东西,但它又是WEB开发中一个很重要的客户端数据来源,而且它可以实现扩展性很好的会话状态, 所以我认为每个WEB开发人员都有必要对它有个清晰的认识。本文将对Cookie这个话题做一个全面的描述, 也算是我对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是如何从服务端写入,最后如何传到服务端以及如何读取的。

Cookie的写、读过程

在Asp.net中,读写Cookie是通过使用HttpCookie类来完成的,它的定义如下:

public sealed class HttpCookie
{
    // 获取或设置将此 Cookie 与其关联的域。默认值为当前域。
    public string Domain { get; set; }
    // 获取或设置此 Cookie 的过期日期和时间(在客户端)。
    public DateTime Expires { get; set; }
    // 获取一个值,通过该值指示 Cookie 是否具有子键。
    public bool HasKeys { get; }
    // 获取或设置一个值,该值指定 Cookie 是否可通过客户端脚本访问。
    // 如果 Cookie 具有 HttpOnly 属性且不能通过客户端脚本访问,则为 true;否则为 false。默认为 false。
    public bool HttpOnly { get; set; }
    // 获取或设置 Cookie 的名称。
    public string Name { get; set; }
    // 获取或设置要与当前 Cookie 一起传输的虚拟路径。默认值为当前请求的路径。
    public string Path { get; set; }
    // 获取或设置一个值,该值指示是否使用安全套接字层 (SSL)(即仅通过 HTTPS)传输 Cookie。
    public bool Secure { get; set; }
    // 获取或设置单个 Cookie 值。默认值为空引用。
    public string Value { get; set; }
    // 获取单个 Cookie 对象所包含的键值对的集合。
    public NameValueCollection Values { get; }
    // 获取 System.Web.HttpCookie.Values 属性的快捷方式。
    public string this[string key] { get; set; }
}

Cookie写入浏览器的过程:我们可以使用如下代码在Asp.net项目中写一个Cookie 并发送到客户端的浏览器(为了简单我没有设置其它属性)。

HttpCookie cookie = new HttpCookie("MyCookieName", "string value");
Response.Cookies.Add(cookie);

我想很多人都写过类似的代码,但是,大家有没有想过:Cookie最后是如何发送到客户端的呢?我们打开Fiddler来看一下吧。

从上图,您应该能发现,我们在服务端写的Cookie,最后其实是通过HTTP的响应头这种途径发送到客户端的。每一个写入动作, 都会产生一个【Set-Cookie】的响应头。
浏览器正是在每次获取请求的响应后,检查这些头来接收Cookie的。

Asp.net获取Cookie的过程:我们可以使用如下代码在Asp.net项目中读取一个Cookie

HttpCookie cookie = Request.Cookies["MyCookieName"];
if( cookie != null )
    labCookie1.Text = cookie.Value;
else
    labCookie1.Text = "未定义";

代码同样也很简单,还是类似的问题:大家有没有想过,Cookie是如何传到服务端的呢?我们还是继续使用Fiddler来寻找答案吧。

从图片中,我们可以发现,Cookie是放在请求头中,发送到服务端的。如果你一直刷新页面,就能发现, 每次HTTP请求,Cookie都会被发送。当然了,浏览器也不是发送它所接收到的所有Cookie,它会检查当前要请求的域名以及目录, 只要这二项目与Cookie对应的Domain和Path匹配,才会发送。对于Domain则是按照尾部匹配的原则进行的。
所以,我在访问 www.cnblogs.com 时,浏览器并不会将我在浏览 www.163.com 所接收到的 Cookie 发出去。

删除Cookie:其实就是在写Cookie时,设置Expires为一个【早于现在时间的时间】。也就是:设置此Cookie已经过期, 浏览器接收到这个Cookie时,便会删除它们。

HttpCookie cookie = new HttpCookie("MyCookieName", null);
cookie.Expires = new DateTime(1900, 1, 1);
Response.Cookies.Add(cookie);

使用Cookie保存复杂对象

前面的示例代码大致演示了Cookie的读写操作。不过,我们平时可能希望将更复杂的【自定义类型】通过Cookie来保存, 那么又该如何操作呢?对于这个问题,我们定义一个类型来看看如何处理。

public class DisplaySettings 
{
    public int Style;

    public int Size;
    
    public override string ToString()
    {
        return string.Format("Style = {0}, Size = {1}", this.Style, this.Size);
    }    
}

上面的代码,我定义一个类型,用于保存用户在浏览页面时的显示设置。接下来,我将介绍二种方法在Cookie中保存并读取它们。

方法-1,经典做法。(注意前面给出的HttpCookie定义代码中的最后二个成员)

private void WriteCookie_2a()
{
    DisplaySettings setting = new DisplaySettings { Style = 1, Size = 24 };

    HttpCookie cookie = new HttpCookie("DisplaySettings1");
    cookie["Style"] = setting.Style.ToString();
    cookie["Size"] = setting.Size.ToString();

    Response.Cookies.Add(cookie);
}

private void ReadCookie_2a()
{
    HttpCookie cookie = Request.Cookies["DisplaySettings1"];
    if( cookie == null )
        labDisplaySettings1.Text = "未定义";
    else {
        DisplaySettings setting = new DisplaySettings();
        setting.Style = cookie["Style"].TryToInt();
        setting.Size = cookie["Size"].TryToInt();
        labDisplaySettings1.Text = setting.ToString();
    }
}

方法-2,将对象JSON序列化为字符串。

private void WriteCookie_2b()
{
    DisplaySettings setting = new DisplaySettings { Style = 2, Size = 48 };

    HttpCookie cookie = new HttpCookie("DisplaySettings2", setting.ToJson());
    Response.Cookies.Add(cookie);
}

private void ReadCookie_2b()
{
    HttpCookie cookie = Request.Cookies["DisplaySettings2"];
    if( cookie == null )
        labDisplaySettings2.Text = "未定义";
    else {
        DisplaySettings setting = cookie.Value.FromJson<DisplaySettings>();
        labDisplaySettings2.Text = setting.ToString();
    }
}

这段代码使用了我定义的二个扩展方法。

对于这二种方法,我个人更喜欢后者,因为它具有更好扩展性:如果类型增加了成员,不需要修改读写Cookie的代码。
不过,这种方式产生的有些字符,比如【双引号】,极少数浏览器(Opera)不支持,所以需要做UrlEncode或者Base64编码处理。
同理,对于第一种方法,遇到Value有【双引号】时,我们同样需要做UrlEncode或者Base64编码处理。

Js中读写Cookie

Cookie并非只能在服务端读写,在客户端的浏览器中也可以实现对它的读写访问。而且在JS中创建的Cookie对于服务端仍然有效(可见), 接下来我们来看看在JS中如何写入Cookie,演示代码将创建一个按钮,并在点击按钮后写入Cookie

<input type="button" onclick="WriteCookie();" value="WriteCookie" />

<script type="text/javascript">
    function WriteCookie() {
        var cookie = "cookie_js=22222222; path=/";
        document.cookie = cookie;
    }    
</script>

在JS中写Cookie很简单,只要给document.cookie赋值一个Cookie字符串即可,至于格式,可以参考前面用Fiddle看到的结果。

再来看一下如何使用JS读取Cookie吧。请参考如下代码:

<input type="button" onclick="ReadCookie();" value="ReadCookie" />

<script type="text/javascript">
    function ReadCookie() {
        alert(document.cookie);
    }    
</script>

仍然是访问document.cookie,不过,这次我们得到却是全部的Cookie值,每个Key/Value项用分号分开,中间则用等号分开。 所以, 如果您想在JS中读取Cookie,一定要按照这个规则来拆分并解析您要读取的Cookie项。鉴于这样的操作有些繁琐, 我们可以jquery.cookie.js插件来轻松完成这个功能,有兴趣的朋友也可以看一下它是如何处理的。 这个插件的代码比较少,这里就直接贴出, 

注意哦:前面我们看到了HttpCookie有个HttpOnly属性,如果它为true,那么JS是读不到那个Cookie的,也就是说: 我们如果在服务端生成的Cookie不希望在JS中能被访问,可以在写Cookie时,设置这个属性。不过,通过一些工具,还是可以看到它们。

接下来,我们再来看看Asp.net中Cookie有哪些应用。

Cookie在Session中的应用

在Asp.net中,HttpContext, Page对象都有个Session的对象,我们可以使用它来方便地在服务端保存一些与会话相关的信息。
前面我们也提到过,HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分 是不是来源于同一个浏览器。 所以,为了实现会话,服务端需要一个会话标识ID能保存到浏览器,让它在后续的请求时都带上这个会话标识ID,以便让服务端知道 某个请求属于哪个会话,这样便可以维护与会话相关的状态数据。由于Cookie对于用户来说,是个不可见的东西,而且每次请求都会传递到 服务端,所以它就是很理想的会话标识ID的保存容器。在Asp.net中,默认也就是使用Cookie来保存这个ID的。注意:虽然Asp.net 2.0 也支持无Cookie的会话,但那种方式要修改URL,也有它的缺点,因此这种方法并没有广泛的使用。本文将不对这个话题做过多的分析, 就此略过无Cookie会话这种方式。

我们来看看Session是如何使用Cookie来保存会话标识ID的,在默认的Asp.net配置中,Web.config有着如下定义:

<sessionState mode="InProc" cookieName="ASP.NET_SessionId" cookieless="UseCookies"></sessionState>

如果我们执行以下操作:

Session["Key1"] = DateTime.Now;

此时,我们可以使用一些浏览器提供的工具来查看一下现在的Cookie情况。

从图片上看,这个Cookie的名字就是我们在配置文件中指出的名称,我们可以修改一下配置文件:

<sessionState cookieName="SK"></sessionState>

再来执行上面的写Session的操作,然后看Cookie

我们可以看到:SK的Cookie出现了。说明:在截图时我把名称为"ASP.NET_SessionId"的Cookie删除了。

通过上面示例,我们可以得到结论,Session的实现是与Cookie有关的,服务端需要将会话标识ID保存到Cookie中。
这里再一次申明,除非你使用无Cookie的会话模式,否则Session是需要Cookie的支持。反过来,Cookie并不需要Session的支持。

Cookie在身份验证中的应用

我想很多人都在Asp.net的开发中使用过Form身份认证。对于一个用户请求, 我们可以在服务端很方便地判断它是不是代表一个已登录用户。

this.labStatus.Text = (Request.IsAuthenticated ? "已登录" : "未登录");

那么,您有没有好奇过:Asp.net是如何识别一个请求是不是一个已登录用户发起的呢?说到这里,我们就要从用户登录说起了。 为了实现登录及Form认证方式,我们需要如下配置:

<authentication mode="Forms" >
    <forms name="UserStatus"></forms>
</authentication>

接下来,我们需要实现用户登录逻辑。具体实现方式有很多,不过,最终的调用都是差不多的,如下代码所示:

private void SetLogin()
{
    System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);
}

只要执行了以上代码,我们就可以看到,前面的判断【Request.IsAuthenticated】返回true,最终会显示"已登录"。 为了探寻这个秘密,我们还是来看一下当前页面的Cookie情况。

果然,多出来一个Cookie,名称与我在配置文件中指定的名称相同。我们再来看看如果注销当前登录会是什么样子的:

private void SetLogout()
{
    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,并认为登录有效。请看代码:

private void SetLogin()
{
    //System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);

    // 下面的代码和上面的代码在作用上是等效的。
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
        2, "fish", DateTime.Now, DateTime.Now.AddDays(30d), false, string.Empty);
    string str = FormsAuthentication.Encrypt(ticket);

    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, str);
    Response.Cookies.Add(cookie);
}

如果执行这段代码,您将发现:【Request.IsAuthenticated】返回true,登录状态会显示"已登录"。
至此,我们可以得出一个结论: Form身份认证依赖Cookie,Asp.net就是每次检查我们在配置文件中指定的Cookie名称,并解密这个Cookie来判断当前请求用户的登录状态。

Cookie的安全状况

从以上图片,您应该能发现:浏览器能提供一些界面让用户清楚的观察我们在服务端写的Cookie, 甚至有些浏览器还提供很方便的修改功能。如下图所示:

所以,我们在服务端写代码读取Cookie时,尤其是涉及类型转换、反序列化或者解密时,一定要注意这些操作都有可能会失败。 而且上图也清楚的反映了一个事实:Cookie中的值都是“一目了然”的,任何人都能看到它们。所以,我们尽量不要直接在Cookie中 保存一些重要的或者敏感的内容。如果我们确实需要使用Cookie保存一些重要的内容,但又不希望被他人看懂, 我们可以使用一些加密的方法来保护这些内容。

1. 对于一些重要性不高的内容,我们可以使用Base64之类的简单处理方式来处理。

2. 对于重要性相对高一点的内容,我们可以利用.net提供的一些加密工具类,自己来设计加密方法来保护。不过, 密码学与加密解密并不是很简单的算法,因此,自己设计的加密方式可能不会很安全。

3. 重要的内容,我们可以使用.net提供的FormsAuthenticationTicket,FormsAuthentication来加密。我认为这种方式还是比较安全的。 毕竟前面我们也看过了,Asp.net的Form身份认证就是使用这种方式来加密用户登录的身份标识的,所以,如果这种方式不安全, 也就意味着Asp.net的身份认证也不安全了。 如果您使用这种方式来加密,那么请注意:它产生的加密后文本还是比较大的, 前面我也提到过,每次请求时,浏览器都会带上与请求相匹配的所有Cookie,因此,这种Cookie会对传输性能产生一定的影响, 所以,请小心使用,切记不可过多的使用。

这里要补充一下:去年曾经出现过【Padding Oracle Attack】这个话题, 一些人甚至错误的认为是Asp.net加密方式不安全!如果您也是这样认为的,那么可以看一下这篇文章: 浅谈这次ASP.NET的Padding Oracle Attack相关内容 ,以消除这个错误的认识。当然了,我们也可以从这个话题得到一些收获:解密失败时,不要给出过多的提示,就当没有这个Cookie存在。

如何在C#发请的请求中使用Cookie

前面我们一直在谈服务端与浏览器中使用Cookie,其实浏览器也是一个普通的应用程序,.net framework也提供一些类也能让我们 直接发起HTTP请求,下面我们来看一下如何在C#发请的请求中使用Cookie ,其实也很简单,主要是使用了CookieContainer类,请看以下演示代码:

private static string SendHttpRequestGet(string url, Encoding encoding, 
            CookieContainer cookieContainer)
    {
        if( string.IsNullOrEmpty(url) )
            throw new ArgumentNullException("url");

        if( encoding == null )
            throw new ArgumentNullException("encoding");

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "GET";
        request.CookieContainer = cookieContainer;
        
        using( WebResponse response = request.GetResponse() ) {
            using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
                return reader.ReadToEnd();
            }
        }
    }

    private void SendHttpDEMO()
    {
        StringBuilder sb = new StringBuilder();
        CookieContainer cookieContainer = new CookieContainer();

        string url = "http://www.taobao.com";
        SendHttpRequestGet(url, Encoding.Default, cookieContainer);

        // 后面可以继续发起HTTP请求,此时将会包含上次从服务器写入的Cookie
        //SendHttpRequestGet("同域名下的其它URL", Encoding.Default, cookieContainer);

        // 至此,我们可以显示取得了哪些Cookie
        CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
        if( cookies != null ) {
            foreach( System.Net.Cookie cookie in cookies )
                sb.AppendLine(cookie.ToString());
        }
        txtCookies.Text = sb.ToString();
    }

重构与使用总结

在前面的Asp.net示例代码中,我一直使用.net提供的HttpCookie类来操作Cookie,是为了展示用原始的方式来使用Cookie, 这些代码有点重复,也有点繁琐, 为此,我提供了几个简单的方法可以更容易的使用Cookie,也算是对Cookie使用的一个总结。

/// <summary>
/// 用于方便使用Cookie的扩展工具类
/// </summary>
public static class CookieExtension
{
    // 我们可以为一些使用频率高的类型写专门的【读取】方法

    /// <summary>
    /// 从一个Cookie中读取字符串值。
    /// </summary>
    /// <param name="cookie"></param>
    /// <returns></returns>
    public static string GetString(this HttpCookie cookie)
    {
        if( cookie == null )
            return null;

        return cookie.Value;
    }

    /// <summary>
    /// 从一个Cookie中读取 Int 值。
    /// </summary>
    /// <param name="cookie"></param>
    /// <param name="defaultVal"></param>
    /// <returns></returns>
    public static int ToInt(this HttpCookie cookie, int defaultVal)
    {
        if( cookie == null )
            return defaultVal;

        return cookie.Value.TryToInt(defaultVal);
    }

    /// <summary>
    /// 从一个Cookie中读取值并转成指定的类型
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cookie"></param>
    /// <returns></returns>
    public static T ConverTo<T>(this HttpCookie cookie)
    {
        if( cookie == null )
            return default(T);

        return (T)Convert.ChangeType(cookie.Value, typeof(T));
    }

    /// <summary>
    /// 从一个Cookie中读取【JSON字符串】值并反序列化成一个对象,用于读取复杂对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cookie"></param>
    /// <returns></returns>
    public static T FromJson<T>(this HttpCookie cookie)
    {
        if( cookie == null )
            return default(T);

        return cookie.Value.FromJson<T>();
    }


    /// <summary>
    /// 将一个对象写入到Cookie
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="name"></param>
    /// <param name="expries"></param>
    public static void WriteCookie(this object obj, string name, DateTime? expries)
    {
        if( obj == null )
            throw new ArgumentNullException("obj");

        if( string.IsNullOrEmpty(name) )
            throw new ArgumentNullException("name");
        

        HttpCookie cookie = new HttpCookie(name, obj.ToString());

        if( expries.HasValue )
            cookie.Expires = expries.Value;

        HttpContext.Current.Response.Cookies.Add(cookie);
    }

    /// <summary>
    /// 删除指定的Cookie
    /// </summary>
    /// <param name="name"></param>
    public static void DeleteCookie(string name)
    {
        if( string.IsNullOrEmpty(name) )
            throw new ArgumentNullException("name");

        HttpCookie cookie = new HttpCookie(name);

        // 删除Cookie,其实就是设置一个【过期的日期】
        cookie.Expires = new DateTime(1900, 1, 1);
        HttpContext.Current.Response.Cookies.Add(cookie);
    }
}

更完整的代码可以从本文的示例代码中获得。(文章底部有下载地址)

使用方式:

public static class TestClass
{
    public static void Write()
    {
        string str = "中国";
        int aa = 25;
        DisplaySettings setting = new DisplaySettings { Style = 3, Size = 50 };
        DateTime dt = new DateTime(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);
    }

    public static void Read()
    {
        HttpRequest request = HttpContext.Current.Request;

        string str = request.Cookies["Key1"].GetString();
        int num = request.Cookies["Key2"].ToInt(0);
        DisplaySettings setting = request.Cookies["Key3"].FromJson<DisplaySettings>();
        DateTime dt = request.Cookies["Key4"].ConverTo<DateTime>();
    }    
}

注意哦:以上代码中都是直接使用字符串"Key"的形式,这种方式对于大一些的程序在后期可能会影响维护。
所以建议:将访问Cookie所使用的Key能有一个类来统一的定义,或者将读写操作包装成一些属性放在一个类中统一的管理。

public static class CookieValues
{
    // 建议把Cookie相关的参数放在一起,提供 get / set 属性(或者方法)来访问,以避免"key"到处乱写

    public static string AAA
    {
        get { return HttpContext.Current.Request.Cookies["Key1"].GetString(); }
    }
    public static int BBB
    {
        get { return HttpContext.Current.Request.Cookies["Key2"].ToInt(0); }
    }
    public static DisplaySettings CCC
    {
        get { return HttpContext.Current.Request.Cookies["Key3"].FromJson<DisplaySettings>(); }
    }
    public static DateTime DDD
    {
        get { return HttpContext.Current.Request.Cookies["Key4"].ConverTo<DateTime>(); }
    }
}

补充

根据一些朋友提供的反馈,这里再补充4个需要注意的地方:

1. 如果使用Form登录验证且希望使用Cookie方式时,建议设置 cookieless="UseCookies", 因为这个参数的默认值是:cookieless="UseDeviceProfile",Asp.net可能会误判。 dudu就吃过亏。

<authentication mode="Forms" >
    <forms name="MyCookieName" cookieless="UseCookies"></forms>
</authentication>

2. Cookie有3个属性,一般我们可以不用设置,但它们的值可以在Web.config中指定默认值:

<httpCookies domain="www.123.com" httpOnlyCookies="true" requireSSL="false"/>

3. 虽然在写Cookie时,我们可以设置name, value之外的其它属性,但是在读取时,是读不到这些设置的。 其实在我的示例代码中有体现,我前面也忘记了说明了。

4. HttpRequest.Cookies 与 HttpResponse.Cookies 会有关系(很奇怪吧)。
以下代码演示了这个现象:

protected void Page_Load(object sender, EventArgs e)
{
    DateTime.Now.ToString().WriteCookie("t1", null);

    label1.Text = ShowAllCookies();

    Guid.NewGuid().ToString().WriteCookie("t2", null);

    // 如果去掉下面代码,将会看到2个t1 
    Response.Cookies.Remove("t1");
    Response.Cookies.Remove("t2");
}

private string ShowAllCookies()
{
    StringBuilder sb = new StringBuilder();

    for( int i = 0; i < Request.Cookies.Count; i++ ) {
        HttpCookie cookie = Request.Cookies[i];
        sb.AppendFormat("{0}={1}<br />", cookie.Name, cookie.Value);
    }

    return sb.ToString();
}

上面的试验代码将会一直显示 t1 的Cookie ,这里就不再贴图了。

本文的所有示例代码可以点击此处下载。

分类: Asp.net
« 博主上一篇:MongoDB实战开发 【零基础学习,附完整Asp.net示例】
» 博主下一篇:细说 Form (表单)
posted on 2011-07-03 20:15 Fish Li 阅读(32954) 评论(147) 编辑 收藏

#98楼 2011-09-22 10:49 | 小 刚Ⅰ  
如果存放sessionid的cookie不是在浏览器中,那么为什么硬盘中不会产生session的相关cookie

浏览器每次重新打开都会产生一个新的sessionid, 关闭后如果存放在服务器中的session还没有过期,那么下次访问还是有效的。不过sessionid已经变了

个人见解
#99楼[楼主] 2011-09-22 12:18 | Fish Li  
Cookie有临时的和永久保存的。
#100楼 2011-10-27 12:17 | Rainbow  
是篇好文章。用心写得文章。
#101楼 2011-11-01 13:45 | 浪长街  
楼主回答粉丝的问题也很用心,不关注都不行
#102楼 2011-11-01 16:07 | 拉拉叟  
private static string SendHttpRequestGet(string url, Encoding encoding, 
CookieContainer cookieContainer)
{
if( string.IsNullOrEmpty(url) )
throw new ArgumentNullException("url");

if( encoding == null )
throw new ArgumentNullException("encoding");

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.CookieContainer = cookieContainer;

using( WebResponse response = request.GetResponse() ) {
using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
return reader.ReadToEnd();
}
}
}

private void SendHttpDEMO()
{
StringBuilder sb = new StringBuilder();
CookieContainer cookieContainer = new CookieContainer();

string url = "http://www.taobao.com";
SendHttpRequestGet(url, Encoding.Default, cookieContainer);

// 后面可以继续发起HTTP请求,此时将会包含上次从服务器写入的Cookie
//SendHttpRequestGet("同域名下的其它URL", Encoding.Default, cookieContainer);

// 至此,我们可以显示取得了哪些Cookie
CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
if( cookies != null ) {
foreach( System.Net.Cookie cookie in cookies )
sb.AppendLine(cookie.ToString());
}
txtCookies.Text = sb.ToString();
}
///////////////////////////////////////////////////////
有个小地方 百思不得其解 望解答
这段代码中 cookieContainer对象只是在request.CookieContainer = cookieContainer; 这行代码中赋值给request.CookieContainer
为什么后来cookieContainer里面有对象了呢?
#103楼[楼主] 2011-11-01 17:37 | Fish Li  
@拉拉叟
CookieContainer是个保存Cookie的容器,.net framework会在收到cookie后写到里面去的。
#104楼[楼主] 2011-11-01 17:38 | Fish Li  
引用浪长街:楼主回答粉丝的问题也很用心,不关注都不行

谢了。
#105楼 2011-11-01 17:39 | 拉拉叟  
谢谢楼主细心的解答
#106楼 2011-11-09 17:18 | Domi.Z  
hi,fish:
请问你文章最后说HttpRequest.Cookies 与HttpResponse.Cookies 会有关系,实在有点摸不着头脑,为什么会有这样的结果呢?
#107楼[楼主] 2011-11-09 18:43 | Fish Li  
@Domi.Z
我的另一篇博客【我心目中的Asp.net核心对象】有介绍。
#108楼 2011-11-28 15:46 | 丫头骗子  
写的很好,学习了。
#109楼 2011-12-21 08:57 | 小东1989  
写的很好,使我加深对cookie的理解,学习中!!!
#110楼 2012-01-04 09:40 | cwg2010  
楼主的排版简洁,内容也不错!必须顶!
#111楼[楼主] 2012-01-04 20:12 | Fish Li  
@cwg2010
多谢
#112楼 2012-01-14 21:15 | xu_happy_you  
很好,很强大!收藏了!
#113楼 2012-01-16 16:44 | dreamhappy  
一个公共方法的类库,每个方法都有注释 (标准的///形式),编译成dll后,在其他项目中引用这个类库,但是方法都没有注释,怎么样才能看到注释呢?
#114楼[楼主] 2012-01-16 22:38 | Fish Li  
@dreamhappy
你要生成包含注释的XML文件的。
项目 -> 属性 -> 生成选项卡中,选择“XML文档文件”
#115楼 2012-01-30 15:20 | 刺客之家  
今天发现了一个特点,就是只有当请求的页面使用了Session对象时,asp.Net才为该请求返回一个SetCookie的Response头信息.
#116楼 2012-02-01 11:40 | Domi.Z  
hi, fish。我问一下,可以不可以把相互依赖的字段 (例如城市ID和国家ID) 放入Cookie? 我目前只把城市ID放到Cookie中避免潜在攻击,但这又给系统增加了复杂性(每个用到国家ID的地方都要先通过城市ID去查找)。目前我的确还没有遇到过有用户篡改Cookie,请问你有没有遇到过类似的需求,你是如何处理的?
#117楼[楼主] 2012-02-01 12:19 | Fish Li  
@Domi.Z
不好意思,不太明白你的业务需求。
使用Cookie时只要记住:它只合适保存较小的数据,且对安全性的要求应该不高。在这个前提条件下,你放什么数据,理论上都是可以的。
#118楼 2012-03-13 10:38 | Franky  
引用Cookie 的限制。 大多数浏览器支持最大为 4096 字节的 Cookie。由于这限制了 Cookie 的大小,最好用 Cookie 来存储少量数据,或者存储用户 ID 之类的标识符。用户 ID 随后便可用于标识用户,以及从数据库或其他数据源中读取用户信息。 浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。大多数浏览器只允许每个站点存储 20 个 Cookie;...


这段话错误很多啊.

1. 大多数浏览器最大支持4096.
IE似乎是4095 , Opera是4096, FF Safari 则是4097.

2. 大多数浏览器只允许每个站点存储 20 个 Cookie.
首先,站点,应该换位域, 然后就是20个的限制. 在2007 年的时候,微软对IE6/IE7 发了一个补丁(KB937143 MS07-045),把cookie数目的上限从20提高到了50 (见http://support.microsoft.com/kb/941495 )。 你可以让IE6/IE7的用户安装这个补丁来解决这个问题。 而IE8默认就是50 个. 至于其他浏览器 FF,Opera,也都是50个. Safari比较特殊. 似乎没有限制. Chrome不知是否演习,没有详测.

3. 当cooke超量时,会丢弃最早的cookie,这个策略是ie,Opera的,firefox则不是.





posted @ 2017-01-16 16:53  suntl  阅读(743)  评论(0编辑  收藏  举报