笔记整理--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在http请求中是明文传递的,除非使用SSL通道,不然是不宜在cookie里放置重要信息。
大小问题。每个cookie的大小不能超过4K。每个Domain下cookie个数根据浏览器不同也不同。
IE7跟IE8限制为50个。
Firefox限制为50个。
Opera限制30个
Safari/WebKit没有限制,但是如果header的大小超过服务器能处理的情况下,则会出现错误。
Safari由于没有Cookie数的限制,所以不作讨论。
在IE和Opera下,将会采用LRU(The Least Recently Used)方法,自动踢掉最老的cookie以腾出空间给新的cookie。IE和Opera使用这个方法。
Firefox就比较独特:它貌似会随机决定哪些cookie将会保留,尽管最后设置的cookie会被保留。所以在Firefox里不要超过cookie数的限制。
Firefox和Safari允许cookie多达4097个字节,其中4096个字节是名字和值,1个字节是=号。
Opera允许cookie多达4096个字节,这些字节包含名字、值和=号。
IE允许4095个字节,这些字节包含名字、值和=号。
每个基于Silverlight的应用程序都被分配了属于它自己的一部分存储空间, 但是应用程序中的程序集却是在存储空间中共享的。一个应用程序被服务器赋给了一个唯一的固定的标识值。基于Silverlight的应用程序的虚拟文件系统现在就以一个标识值的方式来访问了。这个标识值必须是一个常量,这样每次应用程序运行时才可以找到这个共享的位置。
独立存储的APIs 其实和其它的文件操作APIs类似,比如 File 和 Directory 这些用来访问和维护文件或文件夹的类。 它们都是基于FileStream APIs 来维护文件的内容的。
独立存储严格的限制了应用程序可以存储的数据的大小,目前的上限是每个应用程序为1 MB。
它是安全的。也就是说,它和其他的基于安全传输的 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 Session Storage
HTML5 Local Storage
HTML5 Global Storage
HTML5 Database Storage via SQLite
至于哪些cookie会被发送到服务器端,是有一套规则的,例如域名选择、路径选择和Max-Age选择,这些都可以在RFC2109里找到。
每次的http请求,cookie都会包含在包头里发送给服务器,这也是被开发者广为诟病的一个cookie缺点,因为这意味这每个请求都无形中增加了流量,特别是像请求图片这些资源的时候,附带的cookie信息是完全没有必要的。所以现在很多网站图片等静态资源都会用独立的域名运作,这样就可以单独对这些域名进行cookie设置。 除此以外,cookie还有以下影响比较大的缺点:
关于Cookies的一些限制问题,可以参考下Nicholas的一篇文章: 浏览器允许的每个域名下的Cookie数:
那如果Cookie数设置超过限制的时候,各浏览器又是如何处理呢:
cookie的总大小在各浏览器中也是不同的:
注意这里用的字节,也就是,如果是多字节字符,自然就会占用两个字节。在所有浏览器里,如果设置的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中的独立存储有以下一些特征:
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 传输技术相比其他的跨域传输的一些优势:
9、使用HTML5客户端储存数据方法。
HTML5 提供了四种在客户端存储数据的新方法,即
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是一个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协议、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空间的原因是它头部内容比较全
Request Headers:
GET http://user.qzone.qq.com/445235728 HTTP/1.1
Host: user.qzone.qq.com
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://qzone.qq.com/
Accept-Encoding:gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
Cookie:o_cookie=445235728;(省略很多……)
If-Modified-Since: Wed, 13 Jun 2012 01:32:19 GMT
-----------------
Response Headers:
HTTP/1.1 200 OK
Connection:close
Server: QZHTTP-2.34.0
Date: Wed, 13 Jun 2012 02:59:31 GMT
Content-Encoding: gzip
Set-Cookie:login_time=61F0EEA02D704B1DBCF25166A74941B24F4BE24B205C466F;PATH=/;DOMAIN=qzone.qq.com
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
Cache-Control: max-age=0, no-transform
Content-Type: text/html;charset=utf-8
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头域一样。
代码:
<?php
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (time()-strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < 10)) {
header("HTTP/1.1 304 Not Modified");
exit;
}
header("Last-Modified: " . date('D, d M Y H:i:s \G\M\T', time()) );或者header("Last-Modified: " . date('r', time()) );
?>
前者是格林时间格式,后者是中国时间。需要注意的就是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 |
| Maxthon3 | |
点击刷新键 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 |
地址栏回车 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 |
点击后退键 | 同上 | 同上 | 同上 | 同上 |
(2)header(“cache-control: no-cache”)
IE9 | Google17.0 |
| Maxthon3 | |
点击刷新键 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 |
地址栏回车 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 | 重发请求,返回200状态 |
点击后退键 | 同上 | From cache | From cache | 同上 |
(3)header(“cache-control:bublic”)
IE9 | Google17.0 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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的设置。(上面没给出测试内容)
三、 Session和cookies
Session 、cookies是程序员永远讨论的话题之一。
1、简单说一下cookies、session
(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代码如下
session_start();
$_SESSION['favcolor'] = 'green';
$_SESSION['animal'] = 'cat';
$_SESSION['time'] = time();
setcookie("cookie1","www.phpben.com",time()+3600*10);
setcookie("cookie2","www.phpben.com",time()+3600*10);
图片:
结论:
1、 第一次请求是没用cookies的,而第二次有PHPSESSID和两个cookies是因为服务器第一请求返回这个三个cookies。
2、第二次请求比第一次多返回PHPSESSID这个cookies,在第二次则没有了,直到session过期后重新设置。
2、;浏览器关掉cookies,session是否可以正常运行?
前面提及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文件代码:
<?php
echo 'Welcome to page #1<br/>';
session_start();
$_SESSION['favcolor'] = 'green';
$_SESSION['animal'] = 'cat';
$_SESSION['time'] = time();
// Works if session cookie was accepted
echo '<br /><a href="2.php">page 1 (这个href中没SID参数)</a><br/>';
// Or maybe pass along the session id, if needed
echo '<br /><a href="2.php?' . SID . '">page 2 (这个href中有SID参数)</a><br/>';
?>
在2.php文件代码:
<?php
session_start();
echo 'Welcome to page #2<br />';
echo $_SESSION['favcolor'],'<br/>'; // green
echo $_SESSION['animal'],'<br/>'; // cat
echo date('Y m d H:i:s', $_SESSION['time']),'<br/>';
// You may want to use SID here, like we did in page1.php
echo '<br /><a href="1.php">return page 1</a>';
?>
情景1:没禁用浏览器的cookies(用cookies实现session),则在2.php能正常输出
情景2:禁用用浏览器的cookies且在php.ini开启session.use_trans_sid=1,通过1.php第一连接过去显示不了session的值,但第二个连接则正常显示。(说明url回写正常运行)
写一个简单的爬虫 - 如何模拟登录网站 - 从波 - 博客园 - 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)细说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 ,这里就不再贴图了。
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里面有对象了呢?
这段话错误很多啊.
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则不是.
本文来自博客园,作者:suntl,转载请注明原文链接:https://www.cnblogs.com/stlong/p/6290264.html
浏览器每次重新打开都会产生一个新的sessionid, 关闭后如果存放在服务器中的session还没有过期,那么下次访问还是有效的。不过sessionid已经变了
个人见解