这些服务器通常要记录下它们与谁交谈,而不会认为所有的请求都来自匿名的客户端
HTTP最初是一个匿名、无状态的请求/响应服务。服务器处理来自客户端的请求,然后向客户端回送一条响应。Web服务器几乎没有什么信息可以用来判定是哪个用户发送的请求,也无法记录来访用户的请求序列。
后来,Web设计者了提供了一些用户识别机制:
- 承载用户身份信息的HTTP首部
- 客户端IP地址跟踪,通过用户的IP地址对其进行识别
- 用户登录,用认证方式来识别用户
- 胖URL,一种在URL中嵌入识别信息的技术(很少用)
- cookie,一种功能强大而且高效的持久身份识别技术
Web服务器可能会同时与数千个不同的客户端进行对话,我们需要识别哪个socket用来和谁通话,以直到持久连接。本文主要介绍cookie
技术。 其他有关客户端识别技术请参考这里: 客户端识别技术
cookie
cookie是当前识别用户,实现持久会话的最好方式。前面各种技术中存在的很多问题对它们都没有什么影响,但是通常会将它们与那些技术共用,以实现额外的价值。
cookie是客户端会话技术。
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器在发起请求时被携带并发送到服务器上。通常,它用于告知服务器两个请求是否来自同一浏览器,如保持用户的登录状态。cookie使基于无状态的HTTP协议记录稳定的状态信息称为了可能。
Cookie主要用于下面三个方面:
- 会话状态管理(比如用户登录、购物车、游戏分数或者其他需要记录的信息)
- 个性化设置(比如用户自定义设置、主体等)
- 浏览器行为跟踪(比如跟踪分析用户行为等)
cookie曾一度用于客户端数据的存储,因为当时并没有其他何时的存储方法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,cookie渐渐被淘汰。由于服务器指定cookie后,浏览器的每次请求都会携带者cookie数据,会带来额外的开销。新的浏览器API已经允许开发者直接将数据存储到本地
大致可以分为两类:
- 会话cookie:
- 会话cookie是一种临时cookie,它记录了用户访问站点时的设置和偏好。用户退出浏览器时,会话cookie就被删除了
- 持久cookie:
- 持久cookie的生存时间更长一些,它们存储在硬盘上,浏览器退出,计算机重启时它们仍然存在。
通常会用持久cookie维护某个用户会周期性访问的站点的配置文件或者登录名
会话cookie和持久cookie之间的唯一区别在于它们的过期时间。如果设置了Discard参数,或者没有设置Expires或Max-Age参数来说明扩展的过期时间,这个cookie就是一个会话cookie
原理
cookie就像服务器给用户贴的“嗨,我叫”的帖子一样。用户访问一个web站点时,这个web站点就可以读取哪个服务请求贴在用户身上的所有帖子。
- 用户首次访问Web站点时,Web服务器对用户一无所知,Web服务器希望这个用户会再次回来,所以想给这个用户"拍上"一个独有的Cookie,这样以后它就可以识别出这个用户了。
- Cookie中包含了一个由名字-值这样的信息构成的任意列表,然后放进
Set-Cookie
或者Set-Cookie2 HTTP
字段里,随着响应报文一起发给浏览器 - 浏览器收到响应报文,看到里面由Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请求的时候就自动把这个值放进
Cookie
字段里发给服务器。 - 因为第二次请求里有了
Cookie
字段,服务器就知道这个用户不是新人,之前来过,就可以拿出Cookie里的值,识别出用户的身份,然后提供个性化的服务。 - 不过因为服务器的“记忆能力”实在是太差,一张小纸条经常不够用。所以,服务器有时会在响应头里添加多个 Set-Cookie,存储多个“key=value”。但浏览器这边发送时不需要用多个 Cookie 字段,只要在一行里用“;”隔开就行
从上图也可以看到
- cookie是由浏览器负责存储的,而不是操作系统。所以,它是“浏览器绑定的”,只能在本浏览器内生效。
- 如果你换个浏览器或者换台电脑,新的浏览器里没有服务器对应的 Cookie,就好像是脱掉了贴着纸条的衣服,“健忘”的服务器也就认不出来了,只能再走一遍 Set-Cookie 流程。
cookie中可以包含任意信息,但它们通常都只包含一个服务器为了进行跟踪而产生的独特的识别码。
- 比如,服务器会将一个表示
id="34294"
的cookie贴到用户上去。服务器可以用这个数字来查找服务器为其访问者积累的数据库信息(购物历史等)
但是,cookie并不仅限于ID号。很多Web服务器都会将信息直接保存在cookie中,比如:
- 浏览器会记住从服务器返回的Set-Cookie或者Set-Cookies首部中的cookie内容,并将cookie集存储在浏览器的cookie数据库中。
- 将来客户端返回同一站点时,浏览器会挑中那个服务器贴到用户上的那些cookie,并在一个cookie请求首部中将其传回去。
创建Cookie
当服务器收到HTTP请求时,服务器可以在响应头里面加一个Set-Cookie
选项。浏览器收到响应之后通常会保存下Cookie,之后对该服务器每一次请求中都通过Cookie
请求将头部Cookie信息发送给服务器。另外,Cookie的过期时间,域、路径、有效期、适用站点都可以根据需要来指定。
- 响应首部
Set-Cookie
:用于服务器端向客户端发送cookie。- 请求首部
Cookie
:
- 其中含有先前有服务器通过Set-Cookie首部投放并存储到客户端的HTTP cookies
- 这个首部可能会被完全移除,例如在浏览器的隐私设置里面设置为禁用cookie
Set-Cookie响应头部和Cookie请求头部
服务器使用 Set-Cookie 响应头部向用户代理(一般是浏览器)发送 Cookie信息。一个简单的 Cookie 可能像这样:
Set-Cookie: <cookie名>=<cookie值>
服务器通过该头部告知客户端保存 Cookie 信息。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[页面内容]
现在,对该服务器发起的每一次新请求,浏览器都会将之前保存的Cookie信息通过 Cookie 请求头部再发送给服务器。
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
定义 Cookie 的生命周期
Cookie 的生存周期,也就是它的有效期,让它只能在一段时间内可用,就像是食品的“保鲜期”,一旦超过这个期限浏览器就认为是 Cookie 失效,在存储里删除,也不会发送给服务器。
cookie的生命周期可以通过两种方式定义:
- 会话期cookie是最简单的cookie:
- 浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
- 会话期Cookie不需要指定过期时间(Expires)或者有效期(Max-Age)
- 需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期Cookie 也会被保留下来,就好像浏览器从来没有关闭一样,这会导致 Cookie 的生命周期无限期延长。
- 持久性 Cookie 的生命周期取决于过期时间(Expires)或有效期(Max-Age)指定的一段时间
比如:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
提示:当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。
如果您的站点对用户进行身份验证,则每当用户进行身份验证时,它都应该重新生成并重新发送会话cookie,甚至是已经存在的cookie。此技术有助于防止会话固定攻击(session fixation attacks),
Cookie 的作用域
Domain
和Path
标识定义了Cookie的作用域:让浏览器仅发送给特定的服务器和 URI,避免被其他网站盗用。
Domain 属性
Domain
指定了哪些主机可以接受Cookie。如果不指定,默认为origin,不包含子域名。如果指定了Domain
,则一般包含子域名。
例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。
Path属性
Path
标识指定了主机下的哪些路径可以接受Cookie(该URL路径必须存在于请求URL中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。
例如,设置 Path=/docs,则以下地址都会匹配:
- /docs
- /docs/Web/
- /docs/Web/HTTP
限制访问 Cookie
目的:尽量不要让服务器以外的人看到
有两种方法可以确保cookie被安全发送,并且不会被意外的参与者或者脚本访问:Secure
属性和HttpOnly
属性。
- 标记为
Secure
的Cookie只应通过被HTTPS协议加密过的请求发送给服务端,因此可以预防 man-in-the-middle 攻击者的攻击。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障, 例如,可以访问客户端硬盘的人可以读取它。 - JavaScript Document.cookie API 无法访问带有
HttpOnly
属性的cookie;此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS) (en-US)攻击。
示例:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
cookie罐:客户端的状态
cookie的基本思想就是让浏览器积累一组服务器特有的信息,每次访问服务器时都将这些信息提供给它。因为浏览器要负责存储cookie信息,所以此系统被称为客户端侧状态。这个cookie正是名称为HTTP状态管理机制
不同站点使用不同的cookie
浏览器内部的cookie罐中可以有成百上千个cookie,但浏览器不会将每个cookie都发送给所有的站点。实际上,它们通常只向每个站点发送2-3个cookie。原因如下:
- 对所有这些cookie字节进行传输会严重降低性能。浏览器实际传输的cookie字节数要比实际的内容字节数要多
- cookie中包含的是服务器特有的名值对,所以对大部分站点来说,大多数cookie都只是无法识别的无用数据。
- 将所有的cookie发送给所有站点会引发隐私问题,那些你不信任的站点也会获得你只想发给其他站点的信息
总之,浏览器只向服务器发送服务器产生的那些数据。www.a.com产生的cookie只会发送的www.a.com,不会发送给www.b.com。
cookie的域属性
产生cookie的服务器可以向Set-cookie
响应首部添加一个Domain属性来控制哪些站点可以看到那个cookiie。比如,下面的HTTP响应首部就是在告诉浏览器将cookie="mary17"
发送给域.airtravalbargains.com
中的所有站点:
如果用户访问的是www.airtravalbargains.com、specials.airtravalbargains.com或者任意以airtravalbargains.com
结尾的站点,下列cookie首部都会被发布出去:
Cookie : user='mary17'
cookie成分
现在使用的cookie由两个不同的版本:cookies版本0(也叫做Netscape cookie)和cookies版本1(RFC 2965)。后者是对前者的扩展,应用没有前者广泛。
cookies版本0(Netscape)
版本0的Set-Cookie首部
Set-Cooke首部有一个强制性的cookie名和cookie值。后面跟着可选的cookie属性,中间用分号分隔。
NATE-VALUE
:- 强制的
- NAME和VALUE都是字符序列,除非包含在双引号内,否则不包含分号、逗号、等号和空格
- Web服务器可以创建任意的NAME-Value关联,在后继对站点的访问中会将其送回给Web服务器
Set-Cookie:customer=mary
Expires
:- 可选的
- 这个属性会指定一个日期字符串,用来定义cookie的实际生存期。一旦到了过期日期,就不再存储和发布这个cookie了。日期的格式为:
WeekDaye, DD-Mon-YY HH:MM:SS GMT
- 唯一合法的时区为GMT,各日期元素之间的分隔符一定要是长划线。如果没有指定Expires:cookie就会在用户会话结束时过期:
Set-Cookie: foo=bar, expires=Wednesday, 09-Nov-99 23:13:40 GMT
Domain
:- 可选的
- 浏览器只向指定域中的服务器主机名发送cookie。这样服务器就将cookie限制在了特定的域中。acme.com域与av.acme.com和vv.acom.com匹配,但是不与www.cmm.com匹配
- 只有指定域中的主机才能为一个域设置cookie。这些域中至少要有两个或者三个句号,以防止出现.com、.edu、ca.us等形式的域
- 如有没有指定域,就默认为产生Set-Cookie响应的服务器的主机名:
Set-Cookie:SHEYY=MARY; domain="joes-hardware.com"
Path
:- 可选的
- 通过这个属性可以为服务器上特定的文档分配cookie。
- 如果Path属性是一个URL路径前缀,就可以附加一个cookie。路径
/foo
与/foobar
和/foo/aa.html
匹配。路径/
与域中所有内容都匹配 - 如果没有指定路径,就将其设置为产生
Set-Cookie
响应的URL的路径 Set-Cookie:lastorder=001309; path=/orders
Secure
:- 可选的
- 如果包含了这一属性,就只有在HTTP使用SSL安全连接时才会发送cookie:
Set-Cookie:private_id=246; secure
版本0的Cookie首部
客户端发送请求时,会将所有的域、路径和安全过滤器相匹配的未过期cookie都发送个这个cookie。所有cooke都被组合到一个Cookie首部中。
cookies版本1(RFC 2965)
相比版本0,其主要改动包括如下:
cookie版本1的语法如下所示:
cookie与缓存
下面是处理缓存时的一些指导性规则
- 如果无法缓存文档,要将其标识出来
- 文档的所有者最清楚文档是否是不可缓存的。如果不可缓存,就显示的注明------具体来说,如果除了Set-Cookie首部之外的文档是可缓存的,就使用
Cache-Control:no-cache="Set-Cookie"
。另一种更通用的方法是为可缓存文档使用Cache-Control:public
- 文档的所有者最清楚文档是否是不可缓存的。如果不可缓存,就显示的注明------具体来说,如果除了Set-Cookie首部之外的文档是可缓存的,就使用
- public:在HTTP请求返回的过程中,在cache-control设置了public这个值,代表这个HTTP请求返回的内容中所经过的任何路径当中,包括一些中间的HTTP的代理服务器以及发出这个请求的客户端浏览器都可以对这个返回的内容进行缓存的操作。
- private:表示发起请求的这个浏览器才能进行缓存的
- no-cache:每次发送请求都要去服务器验证一下,如果服务器告诉可以使用缓存,才使用本地缓存‘
-
缓存Set-Cookie首部时要小心:
- 如果响应中有Set-Cookie首部,就可以对主体进行缓存(除非被告知不要这么做),但要特别注意对Set-Cookie首部的缓存。如果向多个用户发送了相同的Set-Cookie首部,可能会破坏用户的定位
-
小心处理带有Cookie首部的请求
- 带有cooke首部的请求到达时,就在提示我们,得到的结果可能是私有的。一定要将私有内容标识为不可缓存的,但有些服务器可能会犯错,没有将次内容标识为不可缓存的
- 可以将过期时间设置为0,强制每次进行再验证:
查看cookie
打开存储检查器
F12之后,该工具箱将出现在浏览器窗口的底部,与存储检查激活。它在开发人员工具箱中只是被称为“存储”。
存储检查器用户界面
Storage Inspector UI 分为三个主要组件:
- 存储树
- 表格小部件
- 侧边栏
存储树
存储树列出了 Storage Inspector 可以检查的所有存储类型:
表格小部件
表格小部件显示与所选树项目对应的所有项目的列表。根据存储类型和树项,表中的列数可能会有所不同。
Table Widget 中的所有列都可以调整大小。您可以通过上下文单击表标题并选择要查看的列来隐藏和显示列:
搜索
Table Widget 顶部有一个搜索框:
这会过滤表格以仅显示与搜索词匹配的项目。如果项目的任何字段(包括您隐藏其列的字段)包含搜索词,则项目与搜索词匹配。
添加和刷新存储
您还可以使用按钮添加新的存储条目或刷新当前查看的存储类型的视图(如果适用)(您不能向 IndexedDB 或缓存添加新条目)
侧边栏
当您选择存储表小部件中的任何行时,侧边栏会显示有关该行的详细信息。如果选择了 cookie,它将列出有关该 cookie 的所有详细信息。
侧边栏可以解析 cookie 或本地存储项或 IndexedDB 项的值,并将其转换为有意义的对象,而不仅仅是字符串。例如:
- 字符串化的 JSON’{“foo”: “bar”}'显示为原始 JSON: {foo: “bar”}。
- 包含键分隔值的字符串,如"123~4"或"1=2=3=4"显示为数组:[1, 2, 3, 4]。
- 包含键值对的字符串,例如"ID=1234:foo=bar"显示为 JSON: {ID:1234, foo: “bar”}。
针对chrome
Chrome 开发者工具是查看 Cookie 的有力工具,在“Network-Cookies”里可以看到单个页面 Cookie 的各种属性,另一个“Application”面板里则能够方便地看到全站的所有Cookie。