RFC2109翻译:HTTP状态管理机制: HTTP State Management Mechanism
1. 概述
本文档说明了一种在 HTTP 请求和回复之间创建有状态的会话的手段。它描述了两个新的协议头, Cookie 和 Set-Cookie ,这两个协议头用于在服务器和浏览器之间传递状态信息。此处描述 的方法与 Netscape 的 Cookie提案 不同,但是,它可以与那些使用Netscape 的方法的HTTP/1.0 浏览器交互。(参考 历史 小节。 )
2. 术语
此处所用的术语,“浏览器”( user agent )“客户端”( client )“服务器”( server )“代理”( proxy )和“原始服务器”( origin server ),与 HTTP/1.0 规范中的术语具有相同含义。
完整主机名( Fully-qualified host name ) (FQHN) ,表示:某个主机的完整域名(fully-qualified domain name) (FQDN)( 也就是说, 一个完整的域名,以某个顶级域名作为结尾,例如以 .com 或 .uk结尾) ;或者 ,某个主机的数字式互联网协议(Internet Protocol)(IP)地址。优先使用完整域名 ;强烈反对使用数字式的 IP 地址。
另外两个术语,请求的主机( request-host )和请求的统一资源标识符( request-URI ),分别表示客户端会发送到服务器的以下值:HTTP请求语句 中的绝对统一资源标识符(absoluteURI)(http_URL)的主机(host)( 但不包含端口号 )和绝对路径(abs_path)部分。注意 ,请求的主机必须是一个完整主机名。
主机名可使用 IP 地址来指定,也可使用完整主机名字符串来指定。 有些时候,我们会将一个主机名与另一个主机名相比较。如果 主机A的名字与主机B的名字之间满足以下关系,则我们认为 A对B 是域名匹配(domain-matches)的:
* 两个 主机名都是 IP 地址,并且它们的主机名字符串完全一致;或者
* 两个 主机名都是完整的域名字符串,并且它们的主机名字符串完全一致;或者
* A 是一个完整的域名字符串,并且其形式为 NB ,其中, N 是一个非空的名字字符串, B 的形式为 .B' ,其中, B' 是一个完整的域名字符串。 (于是 , x.y.com 对.y.com是域名匹配的,但是对 y.com 不是域名匹配的。 )
注意,域名匹配是不满足交换律: a.b.c.com 对.c.com是域名匹配的,但是反过来不是。
因为这个词是已经在 Netscape 的原始状态管理实现方案中使用过的, 所以 ,这里我们仍然会使用cookie 这个术语来表示 会在原始服务器和浏览器之间传递,并且被浏览器储存的状态信息。
3. 状态与会话
这篇文档说明的是一种利用 HTTP 请求和回复创建带状态的会话的手段。 目前 , HTTP服务器 在对客户端的请求作出回复时,不会将它们与之前或之后的请求关联起来; 这里所说的这种技巧,使得想要交换状态信息的客户端和服务器将HTTP 请求和回复放入一个更大的上下文中,我们称该上下文为“会话”("session")。 这种上下文 可用来创建一些有用的东西,例如: 一个“ 购物车 ”("shopping cart"), 在最终付款之前,可以将用户选择的商品都集合起来;或者 ,一个杂志浏览系统,用户之前阅读 过的内容会影响到当前要显示什么内容。
当然了,有狠多种潜在的上下文,因此,会存在狠多种潜在的会话类型。 设计 者在考虑通过交换cookies 的方式来实现会话管理时,设想的是以下关键特性:
1. 每个会话 都有一个起始时间和一个终止时间。
2. 每个会话 的生命周期都相对较短。
3. 浏览器或者原始服务器 都可以终止一个会话。
4. 在交换状态信息时,会话就会隐式存在。
4. 概要
我们在这里概要地说明一下,原始服务器如何将状态信息发送给浏览器, 以及,浏览器如何将状态信息返回给原始服务器。设计 的目标是尽可能小地影响到 HTTP协议 和浏览器。只有那些需要维护会话 的原始服务器会有可能受到明显的影响,并且 ,那种影响也主要是限制在通用网关接口(Common Gateway Interface) (CGI)程序 中,除非 该服务器提供了更复杂的状态管理支持。 (参考后 文中的 实现 上的考虑 。 )
4.1 语法:通用语法
这里所用到的两个状态管理协议头, Set-Cookie 和 Cookie ,拥有相同 的语法属性,都是由键/值(attribute-value)对组成。 以下的语法说明中,使用了来自于HTTP/1.1 规范[RFC 2068]的符号(notation)、数字记号(tokens DIGIT)(十进制数字)和记号(token)(通俗 地讲,就是由非特殊字符、非空白字符组成的一个序列 )来描述它们的语法。
av-pairs = av-pair *(";" av-pair)
av-pair = attr ["=" value] ; 值是可选的
attr = token
value = word
word = token | quoted-string
属性( Attributes ) (名字) (attr) 是大小写不敏感的。记号之间允许 有空白字符。注意 ,尽管上这个语法说明中说值是可选的,但是,大部分属性都必须带上值。
注意:以上的这个语法允许属性名字和=号之间有空白。
4.2 原始服务器角色
4.2.1 常规
在需要的情况下,原始服务器会初始化一个会话。 (注意 ,这里所说的“会话”,不是指一个持久的网络连接,而是一个由HTTP 请求和回复组成的逻辑上的会话。 是否存在一个持久的连接,这一点应当对于以cookie为基础的会话没有影响 ) 。 要想初始化一个会话,原始服务器就会返回一个额外的回复协议头给客户端,即为 Set-Cookie 。 (日后 再说细节。 )
如果某个浏览器决定保持某个会话的话,就会返回一个 Cookie请求协议 头(参考后 文说明 )给原始服务器。原始服务器 可以无视它,也可以使用它来确定会话的当前状态。 它可能会回复一个Set-Cookie 回复协议头给客户端,其中带上相同或不同的信息,也可能根本就不发送任何的Set-Cookie协议头。原始服务器 可以向客户端发送一个Max-Age=0 的Set-Cookie 协议头,以直接终止一个会话。
服务器可以在任何一个回复中带上 Set-Cookie 回复协议头。 浏览器应当 在每个请求中都带上Cookie 请求协议头,这是由下文要说的某些限制决定的。
原始服务器可以在单个回复中包含多个 Set-Cookie协议 头。注意 , 中间的网关有可能会将多个这样的协议头精简起来,变成一个协议头。
4.2.2 Set-Cookie语法
Set-Cookie 回复协议头的语法是
set-cookie = "Set-Cookie:" cookies
cookies = 1#cookie
cookie = NAME "=" VALUE *(";" cookie-av)
NAME = attr
VALUE = value
cookie-av = "Comment" "=" value
| "Domain" "=" value
| "Max-Age" "=" value
| "Path" "=" value
| "Secure"
| "Version" "=" 1*DIGIT
通俗地讲, Set-Cookie回复协议 头中包含Set-
Cookie:记号 , 接下来是由逗号分隔的一个或多个 cookies 。每个 cookie 的开头都是一个 NAME=VALUE 对, 接下来是0个或多个以分号分隔的键/值对。之前已经说明 了键/值对的语法。 接下来是特定的属性及它们的语义值。 NAME=VALUE 这个键/值对必须处于每个cookie 的开头。其它 的键/值对,如果存在的话,可以按照任意顺序出现。如果 在 某 个cookie 中 多次出现了同一个属性,则其行为是未定义的。
NAME=VALUE
必需。这条状态信息 ("cookie") 的名字是 NAME ,其值是 VALUE 。 以$开头的名字是保留作为其它用途的, 不允许被应用程序使用。
VALUE对于浏览器 是透明的,可以是原始服务器决定发送的任意内容,一般会是服务器端生成的一串可打印的 ASCII编码字符 串。 "透明"意味 着该内容只对原始服务器有意义。事实 上,可能会有这种情况,就是,对于任何一个检查Set-Cookie协议头的人来说,它都是可读的。
Comment=comment
可选。因为 cookies 中可能包含有与某个用户相关的隐私信息,所以,这个 Cookie属性允许原始服务器注明自己对于 该cookie 的用途。用户 可查看这种注释信息,以决定,是要重新建立一个会话还是继续使用这个cookie 对应的会话。
Domain=domain
可选。域名( Domain )这个属性,指定的是,当前这个 cookie 在哪个域名中是有效的。显式指定的域名必须以小数点开头。
Max-Age=delta-seconds
可选。最长生命周期( Max-Age )这个属性,定义了该cookie的生命周期,单位为秒。 delta-seconds 这个值,是一个十进制的非负整数值。 在过了 delta-seconds 这么多秒之后,客户端应当忽略该 cookie 。如果 值为0,则表示,该 cookie应当立即 被忽略。
Path=path
可选。路径( Path )这个属性,指定的是,这个 cookie 将会对其生效的统一资源定位符集合。
Secure
可选。安全( Secure )这个属性 ( 不带值 ) ,指示浏览器,只能 在 使用 (未明确指定何种手段为安全)安全的手段与原始服务器通信时,才能够发送这个 cookie 。
浏览器(可能在用户的控制下)可以自行决定,什么样级别的安全性才可认为是适合于发送“安全的” cookies 的。 Secure 这个属性,应当看作是服务器向浏览器发出的一个安全性建议,表明 , 在当前会话中应当保护该cookie 的内容。
Version=version
必需。版本号( Version )属性,是一个十进制的整数,表示 的是, 这个是针对哪个版本的状态管理规范的。对于当前文档 所说的规范, Version=1 。
4.2.3 对缓存进行控制
原始服务器必须意识到,所返回的资源和 Set-Cookie 协议头都是有可能被缓存的。对“公共”文档进行缓存是正常的。例如,如果原始服务器想要使用一个公共文档,比如说一个“首页”页面,作为一个标记,表明一个会话开始了,针对该会话,必须生成一个 Set-
Cookie 回复协议头,那么,该页面应当被储存在“已过期的”缓存中,这样,原始服务器才能够接收到后续的请求。 “私有文档”,例如那些包含 着严格属于某个会话的信息的文档,不应当被放置在共享的缓存中。
如果该 cookie 是倾向于被单个用户使用的,那么, Set-cookie协议 头 就不应当被缓存。 一个倾向于被多个用户共享的Set-cookie协议 头 ,可以被缓存。
取决于具体的环境,源服务器应当额外发送以下的 HTTP/1.1回复协议 头:
* 为了阻止对Set-Cookie 协议头的缓存,需要发送 : Cache-control: no-
cache="set-cookie" 。
以及以下几个中的一个:
* 为了阻止浏览器 在共享的缓存中储存某个私有文档,需要发送: Cache-
control: private 。
* 为了允许缓存某个文档,并且要求它在被返回到客户端之前做一次验证,则发送: Cache-control: must-revalidate 。
* 为了允许缓存某个文档,并且要求代理缓存( 而不是浏览器的缓存 )在将它返回到客户端之前做一次验证,则发送: Cache-control: proxy-revalidate 。
* 为了允许某个文档被缓存,并且要求在将它返回到客户端之前做一次验证(通过 将它标记为“已过期的” )的话,则发送: Cache-control: max-age=0 。 并不是所有的缓存都会在所有情况下重新验证文档。
HTTP/1.1服务器 在发送包含Set-Cookie 回复协议头的回复时, 必须 发送 Expires: old-date (其中 , old-date 是狠久之前的某个日子 ) ,除非它们确认 (通过其它渠道)当前 没有下游的 HTTP/1.0代理 。 HTTP/1.1服务器 除了发送Expires: old-date 命令之外,还可以发送其它的Cache-Control 命令,以允许HTTP/1.1代理对内容进行缓存;对于HTTP/1.1代理 , Cache-Control命令 会覆盖掉 Expires: old-date命令。
4.3 浏览器角色
4.3.1 解释Set-Cookie
浏览器会单独跟踪(按照主机名或者IP,加上端口的方式来区分)从每个原始服务器通过 Set-Cookie 回复协议头发送过来的状态信息。对于未发送过来的可选属性,浏览器应当应用以下这些默认值:
Version 默认按照Netscape 指定 的“旧版 cookie ”的行为来进行 。参考 历史 小节 。
Domain 默认 为所请求的主机名( request-host )。 (注意 ,在请求的主机名中,开头处没有小数点。 )
Max-Age 默认行为 是,当浏览器退出时,忽略该 cookie 。
Path 默认 为生成 了该Set-Cookie 回复的请求所对应的统一资源定位符路径,向上直到 最右边的/,但是不要包含该/。
Secure 如果 未指定, 则,浏览器可以通过一个不安全的渠道发送该cookie。
4.3.2 拒绝Cookies
为了阻止可能发生的违反安全性或隐私性的行为,浏览器在满足以下任意一个条件的情况下会拒绝一个 cookie ( 不应当储存它的信息 ) :
* 路径 (Path)属性的值,不是所请求的统一资源标识符(request-URI)的前缀。
* 域名 (Domain)属性的值,其中 不包含嵌套 的 小数点,或者不是以小数点开头的。
* 所请求的主机(request-host)的值,对于域名 (Domain)属性不是域名匹配(domain-match)的。
* 所请求的主机(request-host)是一个完整域名(FQDN)( 不是IP地址 ),并且 , 其形式为 HD ,其中 , D 是域名(Domain)属性的值,并且 H 是一个包含着一个或多个小数点的字符串。
示例:
* 从所请求的主机y.x.foo.com 发送的针对Domain=.foo.com的Set-Cookie 会被拒绝,因为 , H 的值是 y.x ,并且包含着一个小数点。
* 从所请求的主机 x.foo.com发送的针对 Domain=.foo.com 的 Set-Cookie 会被接受。
* 针对Domain=.com 或Domain=.com.的Set-Cookie 永远会被拒绝,因为 ,其中不包含有嵌套的小数点。
* 针对Domain=ajax.com 的 Set-Cookie 会被拒绝,因为,域名 (Domain)的值不是以小数点开头的。
4.3.3 Cookie管理
如果浏览器收到一个 Set-Cookie回复协议 头,其名字(NAME)与已有的某个 cookie相同 ,并且 其域名(Domain)和路径(Path) 值(字符串)也与那个已有的cookie 完全相同,则,这个新的 cookie 会替换掉旧的。但是,如果 该 Set-Cookie 中 Max-Age 的值为0, 则 这个 cookie (包括 旧的和新的 ) 会被忽视掉。其它情况 下, cookies 会持续存在到它们的过期时间(前提是有足够的资源来存储它们),到了过期时间之后,它们会被忽视掉。
因为浏览器中用来存储 cookies 的空间是有限的,所以,它们可能会忽视掉较旧的 cookies 而给较新的 cookies 腾出空间,在这个过程中会使用某种算法,例如 “最近用得最少”(least-recently-used)算法, 同时也会考虑每个原始服务器被允许发送的最大 cookies 数量。
如果某个Set-Cookie回复协议 头 中包含着 Comment属性 ,则,浏览器应当 以一种人眼可读的形式将这个信息与该 cookie储存 在一起,并且应当 在查看cookie 的用户界面上显示出这个注释文字。
浏览器应当允许用户控制 cookie 的销毁过程。某个不太频繁被使用的的可能被某个网络应用程序用作“配置文件”,因而,用户可能会希望,即便它是最近最少被使用的 cookie ,也要保留它。一种可能的实现方式是, 提供 一个界面,允许用户通过复选框来表示要将某个cookie 持久保存(或者,相反地,表示要将某个cookie 立即销毁)。
出于隐私权方面的考虑,要求用户可以方便地控制 cookie 管理过程。 隐私 小节中包含着更多信息。
4.3.4 向原始服务器发送Cookies
浏览器在向原始服务器发送请求时,如果发现本地存在与该请求相匹配的 cookies ,则会向原始服务器发送一个 Cookie 请求协议头。如何判断是否匹配,是依据以下属性的:
* 请求 的主机名;
* 请求 的统一资源标识符;
* 这个 cookie 的生命周期。
这个协议头的语法是:
cookie = "Cookie:" cookie-version
1*((";" | ",") cookie-value)
cookie-value = NAME "=" VALUE [";" path] [";" domain]
cookie-version = "$Version" "=" value
NAME = attr
VALUE = value
path = "$Path" "=" value
domain = "$Domain" "=" value
如果在对应的 Set-Cookie 回复协议头中含有任何的 Version 属性的话,则, cookie-version 属性的值必须与那个属性的值相同。 否则,cookie-version 的值就是0.如果 在对应的Set-Cookie 回复协议头中含有任何的Path 属性的话,则,path 属性的值必须与那个属性的值相同。否则, 在这个Cookie 请求协议头中应当省略掉该属性。 如果 在对应的Set-Cookie 回复协议头中含有任何的Domain 属性的话,则, domain 属性的值必须与那个属性的值相同。否则, 在这个Cookie 请求协议头中应当省略掉该属性。
注意,在 Cookie 请求协议头中,没有与 Set-Cookie 回复协议头中的 Comment 属性对应的属性。 浏览器 不将注释信息返回给原始服务器。
在浏览器本地所有的 cookies 中选择相匹配的 cookie 值的过程,需按照以下规则进行。
Domain 的选择
原始服务器的完整主机名必须针对该 cookie 的 Domain 属性是域名匹配( domain-match )的。
Path 的选择
该 cookie 的 Path 属性,必须是所请求的统一资源标识符( request-URI )的一个前缀。
Max-Age 的选择
那些已经过期的 Cookies应当已经 被忽略了,因此,不会被回发给原始服务器。
如果有多个 cookies 都满足以上列出的条件的话,则,它们会被按照Cookie 协议头中的属性进行排序,使得 ,拥有 更具体的Path 属性的 cookie 会优先排在拥有不那么具体的Path 属性的 cookie 前面。 对于其它属性之间的排序规则(例如,Domain),目前未定义。
注意:出于向下兼容性的考虑, Cookie 协议头中所有地方的分隔符都是分号 (;) 。 出于 向未来版本兼容的考虑,服务器还应当接受在cookie 值之间以逗号(,)作为分隔符。
4.3.5 在无法校验的事务中发送Cookies
用户必须能够控制会话,才能够确保隐私性。 (参考 以下的 隐私 小节。 )然而 ,为了简化实现过程,以避免在已有足够强大的保护措施的情况下又增加一层复杂性,这篇文档会在可校验和不可校验的事务之间作出区分。对于 一个事务,如果在进行该事务之前,用户有机会检查即将被请求的统一资源标识符,则该事务被认为是可校验的。如果用户 没有机会做这种检查,则该事务被认为是不可校验的。 一般在这些情况下会产生不可校验的事务:浏览器自动请求获取内联 的或嵌入的资源;或者,浏览器正在处理来自于原始服务器的重定向(3xx)回应。典型情况 下,最初的事务,即用户所发起的那个事务,是可校验的,而该事务可能会直接或间接地引起浏览器做出不可校验的事务。
在发起不可校验的事务时,浏览器只有在满足以下条件时才能启用一个会话:在最初的事务中曾经发送或者接收过一个其域名( domain )属性的值为D的 cookie , 并且 ,这次的这个不可检验的事务中所请求的统一资源标识符对于 D 是域名匹配的。
这种限制,能够阻止心怀恶意的网站管理员通过不可校验的事务来让浏览器在访问某个服务器的时候启动或者继续使用处于另一个域名中的某个会话。 启动或者继续使用 这种类型的会话,是违反了用户对于隐私权的预期的,并且也可能成为一个安全性问题。
浏览器可以提供一些配置选项,以允许该浏览器或者由该浏览器执行的任何自动化程序忽略掉以上所说的规则, 只要 这些配置选项的默认值是“关闭”("off")就仍然可以确保安全性。
当前 的狠多浏览器已经提供了一种检查功能,可以检查链接的可校验性。 例如,某些浏览器,当鼠标指针被悬停到某个链接上方时,会显示出该链接所指向的目标URL。于是 ,用户在让浏览器真正访问该链接之前,可以先做出决定。 (尽管 在当前的浏览器中还没有实现,但是,对于表单的提交按钮,也可以使用类似的技巧, 当用户准备按下提交按钮时,浏览器可以显示出该表单的提交目标。 )然而 ,这也无法使得所有的链接都成为可检验的;例如,指向自动载入 的图片的链接,一般不会对“鼠标指针”校验作出响应。
狠多浏览器还提供了另一个功能,让用户可以查看文档的 HTML 源代码,或者 将源代码保存到一个外部文件中,以让另一个程序检查它。尽管 这种功能提供了一种简单的检查机制,但是,某些用户并不认为它适合于当前所说的目的。
4.4 原始服务器如何解释Cookie协议头
当 Path 属性与某个新的请求相匹配时,浏览器会将原始服务器之前通过 Set-Cookie 协议头发送过来的大部分信息都回发回去。 原始服务器 在收到 Cookie协议 头时,应当 将那些其NAME的前缀为$的cookies 特殊对待, 将它们作为前一个cookie 的属性来处理。 与这样一个NAME 对应的值,应当 被解释为应用到按照字典顺序(lexically)( 从左到右 )来看最后出现的那个其名字不含$前缀的cookie 上。如果之前 没有 cookie ,则,该值会应用到整个 cookie机制 上。例如,看看以下这个 the cookie
Cookie: $Version="1"; Customer="WILE_E_COYOTE";
$Path="/acme"
$Version应用 到整个 cookie机制 上 (指定 了整个cookie 机制的版本号 ) 。 $Path 是一个属性, 它的值(/acme)定义了某个Path 属性, 当在某个Set-Cookie 回复协议头里定义了Customer cookie 时,便会使用该 Path 属性。
4.5 缓存代理角色
将状态信息与统一资源定位符及文档内容隔离开来的一个原因是, 为了利用缓存功能带来的灵活性。 要想支持 cookies ,缓存代理服务器必须遵守以下这些已经处于HTTP 规范中的规则:
* 有可能的情况下,按照缓存的验证规范,响应来自于缓存的请求。
* 对于 该代理必须向另一个服务器发起的任何一个语法中,都转发一个 Cookie请求协议 头。
* 将回复信息返回给客户端。其中要包含任何存在的 Set-Cookie回复协议 头。
* 按照常规协议 头的控制规则,对接收到的回复内容做缓存,例如以下协议头: Expires 、 Cache-control: no-cache 和 Cache-control: private 。
* 按照常规协议 头的控制规则,对 Set-Cookie命令 做缓存,例如以下协议头: Cache-control: no-cache="set-cookie" 。 (Set-Cookie 这个协议头通常不应当被缓存。 )
代理不能在转发回复信息(请求信息)时自主地加入 Set-Cookie (Cookie)协议 头。
5. 示例
5.1 示例1
请求和回复协议头中的大部分细节都被省略掉了。假设浏览器当前未存储任何 cookies 。
1. 浏览器 -> 服务器
POST /acme/login HTTP/1.1
[表单数据]
用户通过表单来登录,标识自身。
2. 服务器 -> 浏览器
HTTP/1.1 200 OK
Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
Cookie反映 了用户的身份。
3. 浏览器 -> 服务器
POST /acme/pickitem HTTP/1.1
Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
[表单数据]
用户针对“购物车”( "shopping basket" )选择了一项商品。
4. 服务器 -> 浏览器
HTTP/1.1 200 OK
Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1";
Path="/acme"
购物车中现在有一件商品了。
5. 浏览器 -> 服务器
POST /acme/shipping HTTP/1.1
Cookie: $Version="1";
Customer="WILE_E_COYOTE"; $Path="/acme";
Part_Number="Rocket_Launcher_0001"; $Path="/acme"
[表单数据]
用户从表单中选择了商品投递方式。
6. 服务器 -> 浏览器
HTTP/1.1 200 OK
Set-Cookie: Shipping="FedEx"; Version="1"; Path="/acme"
新的 cookie ,反映了投递方式。
7. 浏览器 -> 服务器
POST /acme/process HTTP/1.1
Cookie: $Version="1";
Customer="WILE_E_COYOTE"; $Path="/acme";
Part_Number="Rocket_Launcher_0001"; $Path="/acme";
Shipping="FedEx"; $Path="/acme"
[表单数据]
用户操作,提交订单。
8. 服务器 -> 浏览器
HTTP/1.1 200 OK
交易完成。
浏览器对原始服务器发起了一系列请求,每发出一次请求,就收到了一个新的 cookie 。所有 这些 cookies 的 Path属性 都相同,并且具有相同的 (默认 的 )域名属性。因为 这里所请求的统一资源定位符的前缀都是 /acme ,并且它们 与 Path属性 相匹配,因此 ,每次请求中都带上了目前 为止收到的所有 cookies 。
5.2 示例2
这个示例展示了 Path 属性的效果。与请求和回复协议头相关的所有细节都被省略了。假设浏览器当前未存储任何 cookies 。
假设,针对之前发起的一些请求,浏览器当前接收到了以下回复协议头
Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1";
Path="/acme"
和
Set-Cookie: Part_Number="Riding_Rocket_0023"; Version="1";
Path="/acme/ammo"
在随后,浏览器向(同一个)服务器发送的针对 /acme/ammo/... 这种形式的统一资源定位符的请求中,会包含以下这个请求协议头:
Cookie: $Version="1";
Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo";
Part_Number="Rocket_Launcher_0001"; $Path="/acme"
注意,具有更具体的 Path 属性 /acme/ammo 的那个 cookie ,它的 NAME=VALUE 对被放在前面,而具有不那么具体的 Path 属性 /acme 的那个 cookie ,就被放在后面。 还要注意,相同 的cookie名字出现了多次。
随后,浏览器向(同一个)服务器发送的针对 /acme/parts/ 这种形式的统一资源定位符的请求中,会包含以下这个请求协议头:
Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"
这里,第二个 cookie 的 Path属性/acme/ammo 不是所请求的统一资源定位符/acme/parts/的前缀,因此 ,这个 cookie 不会被回发给服务器。
6. 实现上的考虑
这里,我们考虑一下原始服务器对于状态管理功能的可取的实现方式。
6.1 Set-Cookie内容
原始服务器的内容应当被分割成相分离的程序区域,其中的某些区域需要使用状态信息。 不同的程序区域可通过所请求的统一资源定位符来区分。 Set-Cookie协议 头可与程序区域相关的信息配套使用,具体做法就是针对每个协议头设置好Path 属性。
会话信息可以是明文,也可以是密文,用于描述状态信息。 然而 ,如果它变得过大,那么,其处理过程就会变得狠麻烦。因此 ,在具体的实现中,可能会选择将会话信息表示成服务器侧的某个资源的键。当然 ,使用数据库的话,又会引起某些本意要在这个状态管理规范中处理掉的问题,就是说:
1. 在服务器侧保存真正的状态;
2. 在浏览器终止该会话(例如退出程序)的情况下, 如何 ,以及何时,对数据库中的条目进行垃圾回收。
6.2 无状态的页面
缓存功能有益于提升万维网的可扩展特性。因此,有一点狠重要,就是,减少那些在其中嵌套了状态信息的文档的数量。 例如,如果 一个购物车风格的程序在每个页面上都一直显示着用户购物车中的当前内容,那么,那些页面就无法被缓存,因为每个用户的购物车中内容都是不同的。 另一方面,如果每个页面 上都只是包含着一个带用户去“ 看我的购物车 ”的链接,那么,这种页面就是可以被缓存的。
6.3 实现上的限制
实际中浏览器实现中,会限制它们能够存储的 cookies 的数量和尺寸。一般来讲,浏览器的 cookie 支持功能中,应当没有固定的资源限制。它们应当尽可能多地存储那些频繁使用的 cookies 。此外 ,常规用途 的浏览器应当能够单独满足以下任何一个最小能力要求,尽管不需要同时满足它们:
* 最少支持300 个 cookies
* 每个cookie 中最少可以储存4096 字节的数据 (按照Set-Cookie 协议 头语法说明中的cookie 非术语内容(non-terminal)的长度来计算 )
* 对于每个唯一的主机名或域名,支持最少 20 个 cookies
针对特殊目的或能力受限的设备开发的浏览器,应当能够支持最少 20 个 4096 字节的 cookies ,以确保用户可以与一个基于会话的原始服务器交互。
来自于一个 Set-Cookie 回复协议头中的信息,必须被完整地保留。 如果因为某种原因导致没有足够 的空间来储存这个cookie 的话, 则,应当将它忽视掉,而不是只保留一部分。
网页应用程序应当尽使用可能少的 cookies ,同时应当使用尽可能小的 cookies ,并且,应当完善地处理好某个 cookie 发生丢失的情况。
6.3.1 拒绝服务攻击
浏览器可以选择给针对指定的主机或域名储存的 cookies 的数量设个上限, 或者 给cookie 信息的尺寸大小设个上限。否则, 一个恶意的服务器可能尝试在连续的回复中给浏览器设置狠多cookies 或者巨大的cookies,迫使浏览器删除 掉从别的服务器处接收到的cookies。然而 ,仍然应当支持以上所说的最小数量。
7 . 隐私
7.1用户代理控制
源服务器会创建一个 Set-Cookie 头来跟踪一个用户的路径。用户可能反对这种举动,认为它是一种
信息侵入,即使他们的身份并不明显。(如果一个用户填写了一个明显包含识别信息的表单,那么他的身份就会变得很明显了。)
因此 , 这份状态管理规范文档中, 要求浏览器 给 用户提供 一种手段, 以控制这种可能 发生 的 侵扰,当然 ,我们没有具体 规 定,提供给用户来进行控制的界面应当是什么样的。 然而,提供的控制机制至少应当允许用户:
A.完全禁用发送和保存小甜饼。
B.确定是否有状态会话正在进行中。
C.控制对cookie域属性 的保存
这种控制可以提供机制,例如:
1.当用户代理要发送一个cookie到源服务器 的时候要通知用户,提供不开始一个会话的选项。
2.当有状态会话正在进行中要显示一个指示。
3.当用户关闭窗口或者结束一个会话时,应该由用户去决定哪一个cookie应该被保存
4.让用户在任何时间检查一个cookie内容。
用户代理开始执行而不保存任何的状态信息。
应该可以去配置用户代理永远不发送cookie头,这样它就不能和源服务器保持状态信息。
(这样用户代理就不会去处理 Set-Cookie 头了 )
当用户代理结束运行时,它应该让用户抛弃所有的状态信息。另外,用户代理可能会问用户是否应该将状态信息保留,默认情况应该是不保留。如果用户选择保留状态信息, 这种信息将在下次用户代理运行的时候保存。
注意:用户代理应该谨慎使用文件去存储长期的cookie。如果一个用户运行多个用户代理, Cookie会被混合或者混乱了。
7.2 协议的设计
域属性值的限制,和关于无法验证的事务的规则,是为了减少cookie被泄露给其他网址。
这样做的意图就是为了把cookie限制到一个主机或者一个相关的主机集合。因此,一个请求的主机是收到域值的限制的。我们考虑可接受 host1.foo.com 和 host2.foo.com 共享cookie, 但是 a.com 和 b.com 不可以共享cookie。 同样的,服务器只能为一个相关的cookie设置一个路径。
8. 安全性的考虑
8.1 明文
Set-Cookie 和 Cookie 协议头中的信息是未被保护的。
这样就引起了两个后果:
1. 其中 所包含的任何敏感信息都可以被入侵者看到。
2. 一个恶意的中间节点可能会在协议头被传递的过程中改变它的内容,这将导致不可预料的后果。
这些事实,决定了,与个人和/或经济相关的信息,仅仅应当在安全的通路上发送。 对于 不太敏感的信息,或者,当该协议头的内容是数据库中的某个键时,原始服务器 也 应当仔细处理 ,以避免不正常的 Cookie 值引发系统中的错误。
8.2 Cookie欺骗
正确设计好的网页应用程序,能够避免来自于相关域名的欺骗攻击。 考虑以下场景:
1. 浏览器访问victim.cracker.edu ,获取到了一个 cookie session_id="1234" ,并且将默认域名设置为 victim.cracker.edu 。
2. 浏览器访问spoof.cracker.edu ,获取到了一个 cookie session-id="1111" ,并且其 Domain=".cracker.edu" 。
3. 浏览器再次访问victim.cracker.edu ,并且发送
Cookie: $Version="1";
session_id="1234";
session_id="1111"; $Domain=".cracker.edu"
位于victim.cracker.edu 的服务器应当检测到,第二个 cookie 不是由它自己发出的, 因为Domain属性 不是针对它的,因此应当忽略 它。
8.3 意料之外的Cookie共享
浏览器应当尽一切可能来阻止在属于不同域的主机之间共享会话信息。 对于嵌入 式的或内联的对象,如果它们可被用来在不同的主机之间共享 cookies 的话,则,可能会引起严重的隐私性问题。例如, 一个恶意的服 务器,可能会在一个指向主机b.com 的某个CGI 的URI 中嵌入针对主机a.com 的信息。 强烈建议浏览器在可能的情况下阻止这种形式的数据交换。
9. 其它类似的方案
还有三个其它方案,用于达到类似的目的。 本篇规范文档是 Kristol 的State-Info 方案和 Netscape的Cookie 方案的结合。
Brian Behlendorf设计 了一个方案,其中加入了一个 Session-ID协议 头, 该协议头是由浏览器初始发送的, 可被原始服务器用于跟踪“鼠标点击路径”( "clicktrails" )。然而 ,它不会携带任何由原始服务器定义的状态信息。 Phillip Hallam-Baker设计 了另一个由客户端定义的会话编号机制,起到类似的作用。
尽管会话编号和 cookies 方案都能够维护有状态的会话, 但是,它们倾向的目标是不同的,因而,它们对于隐私性的要求也是不同的。用户初始 化一个会话编号,以让服务器跟踪用户的操作进展,或者区分出同一台机器上的多个用户。 Cookies 是由服务器初始化的,所以 , 这里描述的 cookie机制 ,使得用户 可以控制某些东西,这些东西,在其它方案中对于用户是不可见的。另外 , cookies 可以携带丰富的、由服务器选择的信息, 而会话编号方案呢,只能携带 由用户选择的、简单的信息。
10. 历史
10.1 与网景实现兼容
服务器和客户端使用 Set-Cookie and Cookie 头来映射网景原始的cookie提议。这些记录覆盖新老cookie之间的操作。
10.1.1 扩展的cookie头
建议以一种兼容的方式添加属性-值对到cookie请求头中。老的客户端接收一个新的cookie
会忽略掉它不能理解的属性;它会返回它能理解的给源服务器。新的客户端总是会在新的表单中发送。
一个 收到 新的 cookie 的 老服务器 , 会发现 , 有狠多 cookies 的名字都以$开头,于是 它就忽略它们。 ( 旧的服务器总是会预期cookies 之间以分号分隔,而不是以逗号分隔。 ) 一个新的服务器会检测已经通过老客户端的cookie,因为它们缺少版本属性。
10.1.2 过期和 Max-Age
网景最初建议定义了一个过期头。携带了一个时间值,代替 Max-Age
我们注意到,过期日期格式包含了空格,而老式cookie不支持带有引号的值。
执行了本规范的客户端要注意老cookie和过期时间。
10.1.3 标点符号
在网景最初的建议中,属性值对的取值中是不接受带引号的字符串的。源服务器要谨慎对待
那些需要发送引号的值,除非他们知道接收方的用户代理可以理解他们(比如,新cookie)。
一个新用户代理在cookie应该只用引号括起值,当cookie的版本全部符合这个或者以后的规范。
在网景最初的建议中,空白符是不允许出现在等号附近的。因此在新的实现中,这些空白符需要谨慎使用。
10.2 缓存和 HTTP/1.0
那些符合 HTTP/1.0 的缓存,不可避免的会去缓存 Set-Cookie 头,因为并没有机制去抑制缓存1.1之前的头。这种缓存会导致安全问题。源服务器发送过来的带有 Set-Cookie 头的文档,要不就是不可缓存的,要不就是预过期的。只要缓存服从指令不去缓存文档,这些不可缓存的文档就不会造成任何问题。可是,预过期的文档可能会被保存在缓存里。他们要求验证(带条件的get请求)每一个新的请求,但是有些缓存操作放松了对他们缓存的要求,有些时候服务器不会先去验证他们。这些因素的结合会导致一个用户的cookie发送给了另外一个用户。 Set-Cookie 头被储存在缓存里,尽管文档已经过期,缓存还是会返回文档给之后的请求,包括那些已经缓存的头。
HxLauncher: Launch Android applications by voice commands