笔记整理--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外,到底还有哪些方式能够用来进行“用户识别”。javascript

   一、标准的 Http Cookiephp

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

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

  • 根据请求的url和cookie自己的属性,筛选出须要发送给服务器的cookie(固然也有可能没有cookie)。html5

  • 若是有多个cookie的话,还会决定发送的顺序。java

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

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

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

  • 最后,把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存在种种的不足,才会有新的本地存储技术出现的需求。

       二、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删除掉。

       三、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。

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

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

       五、利用 HTTP ETags 存储Cookie

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

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

       六、在浏览器历史记录中存储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,用户体验很差。获取的时候要遍历,也影响了浏览器的性能。因此不推荐。

       七、使用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的。

       八、使用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)。

  •    九、使用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图:

经过图片能够看出:

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

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

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

二、服务器响应

状态行:协议版本 响应状态 状态描述 ------这三者用空格分开,最后换行回车(\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

为何?

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

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

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()的问题。

2、             浏览器缓存动态

前面介绍了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

同上

结论:

一、刷新对于任何浏览器且无论是什么cache-control,都会从新请求,通常返回是200,除非Last-Modified设置

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

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

四、遨游3的缓存不好

五、IE9 的缓存很强,因此用ie9调试的时候尽量点刷新而不是在地址栏回车

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

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

注意

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

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

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

3、             Sessioncookies

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

一、简单说一下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回写来实现。

注意

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

二、不一样浏览器在同一时间打开赞成文件的sessionID也不同

三、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);  

图片:

结论:

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

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

二、;浏览器关掉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 换为本身的,登陆成功。

一、能够用post方法带上post的数据访问url;

二、也能够用get方法直接访问加了post数据的url;

三、还能够直接复制 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 草案、RFC210九、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)   编辑   收藏
< Prev 1 2 3

#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 阅读( ...) 评论( ...) 编辑 收藏
相关文章
相关标签/搜索