Cookie 规范(RFC 6265)要点翻译
1.简介
本文档定义了 HTTP Cookie 以及 HTTP 头的 Set-Cookie 字段。通过使用 Set-Cookie 头,一个 HTTP 服务器可以传递name/value键值对以及相对应的元数据(所谓的cookies)到user agent。当user agent向服务器发送后续请求时,user agent会根据元数据和其他信息来决定是否要在 Cookie 头中返回 name/value 键值对。
虽然表面上看起来很简单, 但时cookies有很多复杂的地方。例如,服务器在向user agent发送cookie时,对每个cookie会设定一个作用域。 作用域制定了user agent回传cookie的规则:cookie需要回传的最大期限,需要回传cookie到哪些服务器,以及需要应用到哪些模式的URI上。
由于历史原因,cookie包含许多在安全性和隐私上不恰当的地方。例如,服务器可以指定一个给出的cookie字段需要“安全的”连接,但是安全属性并没有保证在存在网络中间人攻击时cookie的完整性。相似的是,给定host的cookies将会被这个host上的所有端口共享,尽管通常来说,浏览器所用的“同源策略”会将从不同端口上取回的东西孤立开来。
这份标准有两类受众:会生产cookie的web服务器的开发者,以及会消费cookie的user agent的开发者。
为了最大化在user agent中的通用性,web服务器在生成cookie时应该把他们自身限制为一个在第4章定义的良好的实现者。
User agent必须实现比第5章中定义的更加宽松的规则,以达到最大化和现有的不符合第4章定义的良好实现者的服务器的互通性。
这份文档说明了在互联网上经常被使用的头的句法和语义。特别地,这份文档并没有创造新的句法和语义。对cookie生成的推荐标准在第4章提供,表述了一些现有服务器行为的子集,在第5章中表述了一些今天并不被推荐的句法和语义,更加宽松的cookie处理算法。某些现存软件的实现和推荐的协议有一些重大的不同,这份文档也包含了一份解释这些不同的内容。
在这份文档之前,至少存在着三份不同的cookie描述:所谓的“Netscape cookie 标准”,RFC 2109, RFC2965。然而,这些文档都没有描述Cookie和Set-Cookie头是如何在互联网上被使用的。根据之前IETF的HTTP状态管理机制的标准,这份文档请求下列操作:
[]将RFC2109的状态改为Historic(已经被RFC2965废止)
[]将RFC2965的状态改为Historic
[]指定RFC2965已经被这份文档废止
特别的是,通过将RFC2965移到Historic并且将其废止,这份文档反对使用Cookie2和Set-Cookie2头。
2.约定
2.1. 一致标准
此处略去几百字
2.2. 句法注解
本文档使用扩充巴科斯范式(ABNF),在RFC5234中有注释。
下列在RFC5234中定义的核心规则被引用,ALPHA(字母),CR(回车),CRLF(CR LF),CTLs(控制字符),DIGIT(数字0-9),DQUOTE(双引号),HEXDIG(十六进制元素 0-9/A-F/a-f),LF(换行符),NUL(空八比特),OCTET(除了NUL以外的所有八比特串),SP(空格),HTAB(水平制表符),CHAR(任意ascii码字符),VCHAR(任意可见的ascii码字符),以及WSP(空白符)。
OWS(可选空白符)规则被用在0个或更多的线性空白符可能出现的场合:
OWS = *([obs-fold]WSP)
obs-fold = CRLF
OWS应该要么不产生要么就产生为一个单独的SP字符。
2.3. 术语
下列术语:user agent,client,server,proxy,origin server(源服务器)和HTTP/1.1标准(RFC2616,1.3节)中的含义相同。
术语request-host是指host的名字,也就是已经被user agent所知的,对user agent来说发送HTTP请求的目的地,或者接收HTTP响应的来源。(也就是发送相应的HTTP请求的host名字)。
术语request-uri已经在RFC2616的5.1.2节中定义。
两个八比特的序列被称为大小写不敏感地相同,当且仅当他们在RFC4790中定义的大小写映射关系被满足时成立。
术语字符串意思是一个非NUL的八位bit序列。
3.概述
这一节概括了一种源服务器将状态信息传递给user agent的方式,也包含了一种user agent将状态信息回传服务器的方式。
为了存储状态,源服务器在HTTP响应中包含了一个Set-Cookie头。在后续的请求中,user agent将回传一个Cookie请求头到源服务器。Cookie头包含了user agent在前面Set-Cookie头中包含的cookie。源服务器可以选择忽略Cookie头或将Cookie用于应用所定义的目的。
源服务器可以在任何响应中发送Set-Cookie响应头。user agent可以在响应码为1xx的请求中忽略Set-Cookie,但必须在除此以外的任何种类响应中处理Set-Cookie(包括响应码为4xx和5xx的响应)。源服务器可以在单个请求的响应中包含多个Set-Cookie字段。Cookie或者Set-Cookie的出现不会阻止存储和复用HTTP请求的缓存。
源服务器不应该把多个Set-Cookie字段打包到单个HTTP头中。通常打包HTTP头的字段可能会更改Set-Cookie字段的语义,因为%x2c(",")字符被Set-Cookie使用,从而在这种打包方式中存在冲突。
3.1. 示例
使用Set-Cookie头,服务器可以向在一个HTTP响应中user agent发送一条短字符串,这条字符串会在未来符合cookie作用域的HTTP请求中回传给服务器。例如,服务器可以给user agent发送一个名叫“SID”的“session标识符”,值为 31d4d96e407aad42。user agent会在后续的请求中回传这个session标识符以及其值。
== Server -> User Agent ==
Set-Cookie: SID=31d4d96e407aad42
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42
服务器可以使用Path和Domain属性变更cookie的作用域。例如,服务器可以委托user agent在每个path每个example.com的子域都返回cookie。
== Server -> User Agent ==
Set-Cookie:SID=31d4d96e407aad42;Path=/;Domain=example.com
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42
就如下一个例子中展示的那样,服务器可以在user agent中存储多个cookie。例如,服务器可以通过返回两个Set-Cookie字段,实现既存储一个session标识符,又存储用户的偏好语言。值得注意的是,服务器用Secure和HttpOnly属性来对更加敏感的session标识符提供额外的安全保护(见4.1.2.)
== Server -> User Agent ==
Set-Cookie: SID=31d4d96e407aad42; Path=/; Secure; HttpOnly
Set-Cookie: lang=en-US; Path=/; Domain=example.com
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42; lang=en-US
注意上面的Cookie头包含了两个cookie,一个名叫SID,另一个为lang。如果服务器希望cookie在user agent的多个“会话“(sessions,例如,user agent重启之后)中持续存在,服务器可以在Expires属性中指定一个过期时间。注意,如果user agent的cookie存储超过它的定额或者用户手动删除了cookie的话,user agent可能会在过期时间到达之前删除cookie。
== Server -> User Agent ==
Set-Cookie: lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42; lang=en-US
最后,为了移除一个cookie,服务器要返回一个把过期时间设置在过去的Set-Cookie字段。服务器只有在Set-Cookie头中Path和Domain属性与创建cookie时相符时,才能成功删除cookie。
== Server -> User Agent ==
Set-Cookie: lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42
4. 服务端的要求
本节描述了“表现良好”的Cookie和Set-Cookie头的句法和语义。
4.1. Set-Cookie
HTTP 响应头中的 Set-Cookie 被用于从服务器向 user agent 发送 cookie。
4.1.1 句法
不正式地说,Set-Cookie响应头包含了名字叫做“Set-Cookie”并跟着的一个“:”以及一个cookie。每个cookie由一个name-value键值对打头,后面跟着0个或者多个attribute-value键值对。服务器不应该发送未能遵从下列语法的Set-Cookie头。
set-cookie-header = "Set-Cookie:" SP set-cookie-string
;Set-Cookie: 之后必须有空格,空格之后才是
;具体的set-cookie-string
set-cookie-string = cookie-pair *( ";" SP cookie-av )
;cookie-pair以及每个cookie-av
;之间分隔符都是";" SP(也就是分号加空格)
cookie-pair = cookie-name "=" cookie-value
cookie-name = token
;token表示的是除了分隔符和CTLs以外的ASCII字符
;分隔符包括:
;小中大尖括号 "("|")"|"[]"|"]"|"{"|"}"|"<"|">"
;空格和水平制表符 SP | HT
;逗号分号冒号引号问号等号 ","|";"|":"|"?"|"="|"""
;斜线: "" | "/"
;@: "@"
cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
; ASCII字符中除了CTRL(控制字符, 空白符
; 双引号, 逗号, 分号, 反斜线()
token = <token, defined in [RFC2616], Section 2.2>
cookie-av = expires-av / max-age-av / domain-av /
path-av / secure-av / httponly-av /
extension-av
expires-av = "Expires=" sane-cookie-date
sane-cookie-date = <rfc1123-date, defined in [RFC2616], Section 3.3.1>
max-age-av = "Max-Age=" non-zero-digit *DIGIT
; In practice, both expires-av and max-age-av
; are limited to dates representable by the
; user agent.
non-zero-digit = %x31-39
; digits 1 through 9
domain-av = "Domain=" domain-value
domain-value = <subdomain>
; defined in [RFC1034], Section 3.5, as
; enhanced by [RFC1123], Section 2.1
path-av = "Path=" path-value
path-value = <any CHAR except CTLs or ";">
secure-av = "Secure"
httponly-av = "HttpOnly"
extension-av = <any CHAR except CTLs or ";">
注意上面的参考文档中某些条目使用了一些和这份文档(ABNF,RFC5234)不同的语法标记。
这份文档没有定义任何关于cookie-value的语义。
为了最大化和user agent的兼容性,服务器在要使用一些任意的数据作为cookie-value时,应该将数据编码,例如使用Base64(RFC4648)。
set-cookie-string中由cookie-av项贡献的部分是被大家熟知的属性。为了最大化和user agent的兼容性,服务器不应该产生在set-cookie-string中有两个属性相同的名字的cookie。(关于user agent如何处理这种情况,见5.3节)
服务器不应该在同一个响应中包含超过一个具有相同cookie-name的Set-Cookie字段。(关于如何处理这种情况,见5.2节)
如果一个服务器向user agent并发地发送了多条包含Set-Cookie头的响应(例如,当在多个sockets上和user agent通信时),这些响应将会创造一个“竞争条件”,最终会导致不可预期的后果。
注意:一些现有的user agent对两位数的年份有不同的处理。为了避免兼容性问题,服务器应该使用要求四位数年份的RFC1123定义的日期格式。
注意:一些user agent会用32位的UNIX time_t来存储和处理日期。time_t相关的库的bug可能会导致这些user agent在2038年之后错误地处理日期。
4.1.2. 句法(非正式)
本节描述了简化的关于Set-Cookie头的语义。这些语义对于要理解最常见的服务器上cookie用法已经足够详细。全部地语义在第5节描述。
当user agent接收到一个Set-Cookie头时,user agent会将cookie及其属性一起存储。随后,当user agent发起HTTP请求时,user agent会在Cookie头中包含合适的并且没有过期的cookie。
如果user agent接收到了一个和某个现有cookie的cookie-name、domain-value和path-value都相同的新cookie,现有的那个cookie将会被驱逐,取而代之的是那个新cookie。注意服务器可以通过向user agent发送一个拥有值为过去某一时刻的Expires属性的新cookie,来删除一个cookie。
除非cookie的属性额外指定,cookie将只会回传到源服务器(例如,不会回传到任何子域上),并且cookie将会在当前会话结束时过期(会话由user agent自己定义)。user agent会忽略未被识别的cookie属性(但不会忽略整个cookie)。
4.1.2.1. Expires属性
Expires属性指明了cookie的最大生命周期,形式为cookie过期的时刻。user agent并不被要求在设定的时间之前保留cookie。实际上,user agent经常由于存储压力或者隐私上的考虑驱逐了cookie。
4.1.2.2. Max-Age属性
Max-Age属性指明了cookie的最大生命周期,形式为cookie过期之前的具体秒数。user agent并不被要求在这段指定的时长内保留cookie。实际上,user agent经常由于存储压力或者隐私上的考虑驱逐了cookie。
注意:某些现有的user agent并不支持Max-Age属性。不支持Max-Age属性的user agent将会直接忽略。
如果cookie既有Max-Age也有Expires属性,Max-Age属性将会有更高的优先级,并且控制cookie的过期时间。如果一个cookie既没有Max-Age也没有Expires属性,user agent将会在本次会话(会话由user agent定义)结束之前保留这个cookie。
4.1.2.3. Domain属性
Domain属性指明了cookie会被发送到哪些host。例如,如果某cookie的Domain属性的值为"example.com",user agent将会在向example.com,www.example.com以及www.corp.example.com(注意,最前面的%x2E("."), 如果出现,将会被忽略,尽管它除了出现在末尾以外都是非法的)发送HTTP请求时,在Cookie头中包含该cookie。如果服务器漏掉了这个Domain属性,user agent只会向源服务器返回cookie。
警告:某些现存的user agent会将不存在Domain属性时,错误地假设为Domain属性存在,并且值为当前的host name。例如,如果example.com返回了一个没有Domain属性的Set-Cookie头,这些user agent将会错误地也向www.example.com发送cookie。
user agent将会拒绝cookie,除非Domain属性为cookie指定的作用域会包含源服务器。例如,user agent将会接受一段来自“foo.example.com”的Domain属性为"example.com"或者"foo.example.com"的cookie,但是user agent不会接受Domain属性为"bas.example.com"或者"baz.foo.example.com"的cookie。
注意:出于安全的原因,许多user agent被设定为拒绝Domain属性对应为"公共结尾"的cookie。例如,一些user agent将会拒绝Domain属性为"com"或"co.uk"等。(详见5.3节)
4.1.2.4. Path属性
每个cookie的作用域被限定到了由path组成集合中,由Path属性控制。如果服务器没有提供Path属性,user agent将会使用当前的require-uri中path元素的“目录”作为默认值(更多细节详见5.1.4节)
user agent会在一次HTTP请求中包含该cookie,条件是require-uri中路径的部分匹配Path属性(或者是Path的子目录),其中%x2F("/")被解释为路径分隔符。
虽然这看起来对分隔同一host中不同路径的cookie十分实用,但是Path属性不能作安全的凭据。(见第8节)
4.1.2.5.Secure属性
Secure属性将cookie的作用域限定到“安全的”传输途径(“安全的”是有user agent所定义的)。当一个cookie拥有Secure属性时,user agent只有在请求是从一个安全的传输途径(典型的是以TLS方式传输HTTP,也就是HTTPS,RFC2818)传输时,才会发送该cookie。
尽管这看起来对保护cookie以免受中间人攻击很有用,但是Secure属性只在机密性上保护了cookie。一个网络中间攻击者可以通过非安全的传输途径覆盖该cookie,从而破坏其完整性。(详见8.6节)
4.1.2.6. HttpOnly属性
HttpOnly属性将cookie的作用域限制到HTTP请求中。尤其是,这个属性委托user agent在提供非HTTP方式访问cookie时(例如,浏览器提供给脚本访问cookie的接口),忽略该cookie。
4.2. Cookie
4.2.1. 句法
user agent会将存储的cookie放在Cookie头中发送给源服务器。如果服务器遵从4.1中的要求(并且user agent遵从第5节的要求),user agent将会发送符合下述语法的Cookie头部:
cookie-header = "Cookie:" OWS cookie-string OWS
cookie-string = cookie-pair *( ";" SP cookie-pair )
4.2.2. 语义
每个cookie键值对都表述了一个被user agent保存的cookie。cookie键值对包括从Set-Cookie头中接收到的的cookie-name和cookie-value。
注意cookie的属性没有被返回。尤其是,服务器不能单靠Cookie头部就能确定,什么时候cookie会过期,cookie对哪些host有效,对什么路径有效,还有cookie是否设置了Secure或者HttpOnly属性。
每个单独的cookie的语义没有在该文档中定义。服务器被期望以应用相关地语义来填充cookie。
虽然cookie在Cookie头中被线性地序列化,但是服务器不应该依赖序列化的顺序。尤其是,当Cookie头中包含了两个具有相同名字的cookie时(例如,被设置成不同Path或者Domain属性但拥有相同名字的cookie),服务器不应该依赖这些cookie在头部中出现的顺序。
5. User Agent的要求
本节用具体地细节说明了Cookie和Set-Cookie头部,使得实现这些要求的user agent可以和现存的服务器(即使那些不满足第4节中要求的)进行互操作。
5.1. 子元素算法
本节定义了user agent所用的用于处理Cookie和Set-Cookie头部子元素得一些算法。
5.1.1. 日期
user agent必须使用一个和下述算法等价的算法来实现解析cookie-date。注意下述的各种被定义为算法一部分的boolean-flag(例如,found-time,found-day-of-month, found-month,found-year)最初状态是“没有设定”)。
1.使用下述算法,将cookie-date分割成date-token
cookie-date = *delimiter date-token-list *delimiter
date-token-list = date-token *( 1*delimiter date-token )
;date-token由终结符分隔
date-token = 1*non-delimiter
delimiter = %x09 / %x20-2F / %x3B-40 / %x5B-60 / %x7B-7E
non-delimiter = %x00-08 / %x0A-1F / DIGIT / ":" / ALPHA /%x7F-FF
;此处的非终结符指的就是非ascii,以及ascii码中的不可见字符
;以及字母和数字,以及:
;也就是说非终结符在可见ascii字符中只包括数字字母和冒号
non-digit = %x00-2F / %x3A-FF
day-of-month = 1*2DIGIT ( non-digit *OCTET )
month = ( "jan" / "feb" / "mar" / "apr" /
"may" / "jun" / "jul" / "aug" /
"sep" / "oct" / "nov" / "dec" ) *OCTET
year = 2*4DIGIT ( non-digit *OCTET )
time = hms-time ( non-digit *OCTET )
;可以看出时间之后必须要有一个数字来分隔
;但是再往后是什么都无所谓了
hms-time = time-field ":" time-field ":" time-field
time-field = 1*2DIGIT
2.按照下述顺序出列每个在cookie-date中出现的token:
1. 如果found-time的flag还没有设定并且token匹配了time产生式,设定found-time的flag,并且相应地 设定hour、minute、second为token所表示的值。跳过下面剩余的步骤,并继续处理下一个 date-token。
2. 如果found-day-of-month的flag还没有被设定,并且token匹配了day-of-month的产生式,设定 found-day-of-month的flag,并且相应地设定day-of-month为token所表示的值。跳过下面剩余的步骤, 并继续处理下一个date-token。
3. 如果found-month的flag还没有被设定,并且token匹配了month的产生式,设定found-month的flag, 并且相应地设定month为token所表示的值。跳过下面剩余的步骤,并继续处理下一个date-token。
4. 如果found-year的flag还没有被设定,并且token匹配了year的产生式,设定found-year的flag, 并且相应地设定year为token所表示的值。跳过下面剩余的步骤,并继续处理下一个date-token。
3.如果year的值大于70且小于等于99,在year的值的基础上增加1900.
4.如果year的值大于0且小于等于69,在year值得基础上增加2000.
- 注意:某些现有的user agent会用不同的方式解析两位数的年份。
5.在下列情况中,终止这些步骤并且解析cookie-date以失败告终:
- [*] 至少在 found-day-of-month,found-month,found-year,或者 found-time 的 flag 中有一个没有设定
- [*] day-of-month的值小于1或者大于31
- [*] year的值小于1601
- [*] hour的值大于23
- [*] minute的值大于59
- [*] second的值大于59
注意在这个句法中闰秒不能被体现
6.将解析出的 cookie-date 转换为 UTC 时间。如果这个日期不存在,终止算法并且以失败告终。
7.将转换出的时间作为算法的结果返回。
5.1.1. 规范化 host 名
一个规范的host名是通过下述算法生成的字符串:
1. 将host名转换为独立域名标签序列.
2. 将每个不属于LDH非保留字标签的标签,转换为A标签详见[RFC5890] 2.3.2.1),或者转换为“punycode 标签”(RFC3490的第四节所定义的“TOASCII”方法).
3. 将最终的标签序列拼接,中间以%x2E(".")分隔.
5.1.3. Domain 匹配
只有在满足下列条件之一时:
A. domain 字符串和给定的字符串相同。(注意两者会被规范化为
小写之后进行比较)
B. 当满足下列所有条件时:
[*] domain字符串是给定字符串的一个尾部子串
[*] 给定字符串不包含在domain字符串中的最后一位字符是%X2E(".")
[*] 该字符串是一个host name(也就是说,不是一个ip地址)
给定的字符串才匹配给定的domain字符串的domain。
注意此处Domain是指hostname,是不包含端口号的
5.1.4. 路径和路径匹配
user agent必须使用和下述算法等价的算法来计算cookie的默认路径:
1. 将uri-path设定为require-uri中的path部分,如果path不存在, 则设为空。
例如,如果request-uri刚好包含了path(以及可选的query string),那么
uri-path就是该path(不包括%X3F("?")字符或者query string),而当request-uri
包含一个完整的绝对路径时,uri-path就是URI的path元素。
2. 如果uri-path为空,或者uri-path的第一位不是%X2F("/")字符,输出%x2F并且
跳过剩余的步骤。
3. 如果uri-path只包含不超过一个%x2F("/"),输出%x2F并且
跳过剩余的步骤。
4. 输出uri-path从左边数第一位到最后一个%x2F("/")之间的字符串,但是结果不
包含最后一个%x2F("/")
至少满足下面的一个条件时,request-path才匹配cookie-path的路径:
[*] cookie-path和request-path相等
[*] cookie-path是request-path的前缀,并且cookie-path的最后一位是%x2F("/")
[*] cookie-path是request-path的前缀,并且第一个不包含在cookie-path的
字符是%x2F("/")
5.2. Set-Cookie 头
当user agent在一次HTTP响应中接收到一个Set-Cookie字段时,user agent可能会完全会略Set-Cookie字段。例如,user agent可能希望禁止“第三方”cookie(见7.3节)。
如果user agent没有完全忽略Set-Cookie字段,那么user agent必须解析将Set-Cookie头中每个字段的值作为cookie-string解析。(下面将定义)
注意:下述算法比4.1节中的语法更加宽松。例如,该算法会将cookie的name-value的头尾空白符
移除(但保留中间的空白符),不过4.1节中的语法禁止了这些地方的空白符。user agent使用这个算法来和哪些不符合第四节中的推荐的服务器进行互操作。
user agent必须使用和下面所述的算法等价的算法来解析set-cookie-string:
1. 如果set-cookie-string包含%x3B(";"), 那么name-value-pair由set-cookie-string
开头到第一个%x3B(“;”)组成,但不包含%x3B,并且unparsed-attributes由剩下的set-cookie-string 组成(包括%x3B); 否则,name-value-pair就是set-cookie-string,并且
unparsed-attributes时空串。
2. 如果name-value-pair缺少%x3D("=")字符,那么完全忽略set-cookie-string。
3. name字符串(可能为空)由name-value-pair的开头,到第一个%x3D("=")组成,但不包含%x3D, 同时value(可能为空)由第一个%x3D之后的所有字符组成。
4. 移除name和value的开头和结尾处的所有WSP(空白符)。
5. 如果name为空,完全忽略set-cookie-string。
6. cookie-name就是name字符串,cookie-value就是value字符串。
user agent必须使用和下面所述的算法等价的算法来解析unparsed-attributes:
1. 如果unparsed-attributes为空,跳过剩下的这些步骤。
2. 丢弃unparsed-attributes的第一个字符串(也就是%x3B)。
3. 如果剩下的unparsed-attributes中包含%x3B(“;”),那么消耗从开头到第一个%x3B
的字符串(但不包含);否则,消耗剩下的所有unparsed-attributes。之后,令
这个步骤中消耗的字符串为cookie-av。
4. 如果cookie-av包含一个%x3D(“=”),那么attribute-name(可能为空)由开头
到第一个%x3D组成,并且不包含%x3D,同时attribute-name(可能为空)为第一个
%x3D之后的字符串;否则,attribute-name的值为整个cookie-av,并且attribute-value
为空。
5. 移除attribute-name和attribute-value的首尾空格。
6. 根据下面子章节所述的要求解析attribute-name和attribute-value(注意,未被
识别的attribute-name将会被忽略)。
7. 回到算法的第一步。
当user agent完成对set-cookie-string的解析时,就说 user agent 从 require-uri 中获取到了名字为 cookie-name,值为cookie-value,以及属性为cookie-attribute-list的cookie。(由接收到cookie所触发的额外的要求详见5.3节)
5.2.1. Expires属性
如果attribute-name大小写不敏感地匹配了字符串 Expires,user agent 必须按照下列步骤处理 cookie-av。
令expiry-time的值为将attribute-value按cookie-date(见5.1.1节)解析后的值。
如果attribute-value没有成功地解析成一个cookie date,那么忽略这个cookie-av。
如果expiry-time晚于user agent可以表达的最晚的时间,user agent可以将 expiry-time 替换为 user agent 可以表达的最晚的时间。
如果expiry-time早于user agent可以表达的最早的时间,user agent可以将 expiry-time 替换为能表达的最早的时间。
向cookie-attribute-list追加一个属性名为Expires,属性值为expiry-time
的属性。
5.2.2. Max-Age属性
如果attribute-name大小写不敏感地匹配了字符串“Max-Age”,user agent 必须按照下列步骤处理cookie-av。
如果attribute-value的第一个字符不是DIGIT(数字)或者“-”,那么忽略cookie-av。
如果attribute-value剩余的字符中包含一个非DIGIT(非数字)字符,那么忽略这个cookie-av。
令delta-seconds为attribute-value转换为整数之后的值。
如果delta-seconds小于或等于0,令expiry-time为最早可以表达的日期和时间。否则,令expiry-time为当前的日期和时间加上delta-seconds的秒数。
向cookie-attribute-list追加一个属性名为Max-Age,属性值为expiry-time的属性。
5.2.3. Domain属性
如果attribute-name大小写不敏感地匹配了字符串“Domain”,user agent必须按照下列步骤处理cookie-av。
如果attribute-value是空值,那么行为是未知的。然而,user agent应该忽略整条cookie-av。
如果attribute-value的第一个字符是%x2E("."),令cookie-domain为除去attribute-value第一个%x2E(".")之后的值;否则,令cookie-domain的值为整个attribute-value。
将cookie-domain转换为小写。
向cookie-attribute-list追加一个属性名为Domain,属性值为cookie-domain的属性。
5.2.4. Path属性
如果attribute-name大小写不敏感地匹配了字符串“Path”,user agent必须按照下列步骤处理cookie-av。
如果attribute-value时空值或者attribute-value的第一个字符不是%x2F(“/”),那么令cookie-path为默认路径;否则,令cookie-path为整个attribute-value。
向cookie-attribute-list追加一个属性名为Path,属性值为cookie-path的属性。
5.2.5. Secure属性
如果attribute-name大小写不敏感地匹配了字符串“Secure”,user agent必须向cookie-attribute-list追加一个属性名为Secure,属性值为空的属性。
5.2.5. HttpOnly属性
如果attribute-name大小写不敏感地匹配了字符串“HttpOnly”,user agent必须向cookie-attribute-list追加一个属性名为HttpOnly,属性值为空的属性。
5.3. 存储模型
user agent的每个cookie会存储下列所述的字段:name,value,expiry-time,domain,path,creation-time,last-access-time,persistent-flag,host-only-flag,secure-only-flag,以及http-only-flag。
当user agent从一个request-uri接受了一个拥有名叫cookie-name,值为cookie-value,以及属性为cookie-attribute-list的cookie时,user agent必须像下面这样处理cookie:
1. user agent可能完全忽略某个接收到的cookie。例如,user agent可能希望禁止掉来自
第三方的cookie,或者user agent不希望存储超过某个大小的cookie。
2. 新建一个名字为cookie-name,值为cookie-value的新cookie。将creation-time和
last-access-time设定为当前日期和时间。
3. 如果cookie-attribute-list包含一个属性名为"Max-Age"的属性,那么将cookie
的presistent-flag设为true,将cookie的expiry-time设定为cookie-attribute-list中
最后一个属性名为Max-Age的属性的属性值;
否则,如果cookie-attribute-list中包含一个属性名为“Expires”的属性 (并且不包含“Max-Age”属性) ,那么将cookie的presistent-flag设定为true,并且将cookie的expiry-time
设定为cookie-attribute-list中最后一个属性名为Expires的属性值;
否则,将cookie的presistent-flag属性设为false,并且将cookie的expiry-time设定为
能表达的最远的日期。
4. 如果cookie-attribute-list包含一个属性名为“Domain”的属性,令domain-attribute
为cookie-attribute-list中最后一个属性名为Domain的属性值;否则,令domain-attribute为空串。
5. 如果user agent被配置为拒绝“公共后缀”,并且domain-attribute的值为某个
公共后缀时:如果domain-attribute和规范化后的request-host相同的话,令domain-
attribute属性为空串;否则,忽略整个cookie病跳过这些步骤。
> 注意: “公共后缀”是一个由公开注册机构控制的域名,比如说“com”,"co.uk",以及
> "pvt.k12.wy.us"。一个步骤对于预防从attacker.com,通过设置一个Domain属性
> 为"com"的cookie,来破坏example.com的cookie完整性很重要。不幸的是,
> 公共后缀(著称的注册商控制的域名)的集合随时间发生着变化。如果可能的话,user agent
> 应该用一份最新的公共后缀列表,例如有Mozillas维护的一份列表(https://publicsuffix.org/)。
6. 如果domain-attribute非空:如果规范化之后的request-host不匹配domain-attribute
中的域名,那么完全忽略掉cookie并且终止这些步骤;否则,将cookie的host-only-flag
设定为false,并且将cookie的domain设定为domain-attribute。
否则:将cookie的host-only-flag设定为true,并且将domain设定为规范化之后的request-host。
7. 如果cookie-attribute-list包含一个属性名为“Path”的属性,将cookie的path
属性设定为cookie-attribute-list中最后一个属性名为“path”的属性值;否则,将
cookie的path设定为request-uri的默认路径。(5.1.4节)
8. 如果cookie-attribute-list包含一个属性名为“Secure”的属性时,将cookie的
Secure-only-flag设定为true;否则,设为false。
9. 如果cookie-attribute-list包含一个属性名为“HttpOnly”的属性时,将cookie的
http-only-flag设定为true;否则,设为false。
10. 如果cookie是从非HTTP的API传入的,并且设定了cookie的http-only-flag,那么
终止这些步骤并且完全忽略该cookie。
11. 如果cookie的存储中已经包含了一个和新建的cookie的name、domain、path
都相同的cookie,那么:a. 令old-cookie为现存的domain、name、path都相同的
cookie(注意这个算法保持了至多只有一个这样cookie的不变性);b. 如果新创建的
cookie是从一个非HTTP的API接收到的并且old-cookie的http-only-flag已经设定,
那么终止这些步骤并且完全忽略这个新创建的cookie;c. 将新创建的cookie的creation-time
更新为old-cookie的creation-time;d. 将old-cookie从cookie 存储中移除。
12. 将新创建的cookie插入到cookie存储中。
当cookie的过期时间是在过去时,这个cookie就是“过期的”。
user agent必须从cookie存储中清除掉所有的过期cookie,条件为在任何时刻,cookie存储中存在一个过期的cookie时。
在任何时刻,user agent都可能会从cookie存储中“移除过量的cookie”,条件为共享同一个domain字段的cookie的数量超过了预设的边界值(例如50个cookie)。
在任何时刻,user agent都可能会从cookie存储中“移除过量的cookie”,条件为所有cookie的总数量超过了预设的边界值(例如3000个cookie)。
当user agent从cookie存储中移除过量的cookie时,user agent必须按下面的优先级清除cookie:
- 过期的cookie
- 共享一个Domain的cookie数目超过其他cookie预设的一个数量(?)时
- 所有的cookie
如果两个cookie具有相同的移除优先级,那么user agent必须先移除last-access更早的那个cookie。
当“当前会话结束”(由user agent定义)时,user agent必须从cookie存储中
移除所有persisitent-flag属性为false的cookie。
5.4. Cookie头
user agent将会在HTTP请求的Cookie头中包含存储的cookie。
当user agent产生一个HTTP请求时,user agent禁止产生多余一个的Cookie头字段。
user agent可能会在HTTP中删除Cookie头部。例如,user agent可能希望禁止
发送从第三方请求中获取到的cookie。(见7.1节)
如果user agnet已经把一个Cookie头部字段添加到HTTP头部中,user agent必须把cookie-string(由下面定义)当做这个头部字段的值发送。
user agent必须用一个等价于下面所述的算法来从cookie存储中结合request-uri,来
计算“cookie-string”:
1. 令cookie-list为cookie存储中符合下面所有要求的cookie的集合:
[*] 要么,cookie的host-only-flag为true,并且规范化后的request-host和
cookie的域名相同;或者,cookie的host-only-flag为false,并且规范化后
的request-host和cookie的domain匹配;
[*] request-uri的path部分和cookie的path部分相匹配;
[*] 如果cookie的secure-only-flag为true, 那么request-uri的scheme
必须表示一个“安全的”协议(由user agent定义)。
> 注意:“安全的”协议不是由本文档定义的。典型的情况是,user agent会将使用
安全传输的协议认为是安全的,比如SSL或者TLS。例如,大多user agent会将“https”
看作是表示安全协议的scheme。
[*] 如果cookie的http-only-flag被设定为true,那么这个cookie将会在为“非HTTP”
接口(由user agent定义)生产cookie-string时被忽略。
2. user agent应该按照下列顺序对cookie-list进行排序:
[*] 拥有更长的path的cookie将会排在拥有更短的path的cookie前面。
[*] 当cookie拥有相同的path字段长度时,拥有更早creation-time的cookie将会
被排在更晚creation-time的cookie前面。
> 注意:不是所有的user agent都按这个吮吸排列cookie-list,但是这个顺序
反应了在撰写本文档时以及历史上,现有的服务器(错误地)所依赖的顺序的经验。
3. 将cookie-list中每个cookie的last-access-time更新为当前的时刻。
4. 将cookie-list序列化为一个cookie-string,需要按照下列顺序处理每个cookie:
1. 输出cookie的名字,%x3D("=")字符,以及cookie的值.
2. 如果在cookie-list中还存在未处理的cookie,输出字符%x3B以及%x20("; ").
>注意:除了它的名字,cookie-string其实是一个八bit序列,而不是字符序列。为了将
cookie-string(或者其中的元素)转换为字符序列(例如,为了展示给用户),user agent
可能希望尝试使用UTF-8编码(RFC3629)来解码这个八bit序列。但是,这个解码可能会失败,因为
不是所有的八bit序列都是合法的UTF-8.
6. 实现上的考虑
6.1. 限制
实际的user agtn实现在他们能存储的cookie的数目和大小上有限制。常见的user agent应该提供下述的最小容量:
- [] 每个cookie至少4096bytes(以cookie的name,value和attribute之和衡量)
- [] 每个domain至少50个cookie
- [*] 至少总共3000个cookie
服务器应该使用尽可能小和尽可能小的cookie来防止达到这些实现上的限制,以及最小化网络带宽的需求,因为Cookie头会在每一个请求中都被包含。
如果user agent未能在Cookie头中返回一个或者多个cookie,服务器应该优雅地处理这种情况,因为user agent随时可能会按照用户的要求移除任何cookie。
6.2. API
Cookie和Set-Cookie头使用一个这么难懂的句法的原因是,许多平台(包括服务器和user agent)都提供了一个基于字符串的有关cookie的API,这就要求应用层的开发者自己生成和解析Cookie和Set-Cookie头,这导致许多程序员错误地实现了,最终导致不兼容问题。
为了替代提供基于字符串的cookie相关API的做法,平台最好提供更加语义化的API。推荐详细的API设计超过了本文档的范畴,但是显然接收一个抽象的"Date"对象而不是一段序列化的date string会有清晰的好处。
6.3. IDNA依赖与迁移
IDNA2008(RFC5890)取代了IDNA2003(RFC3490)。然而,在两份说明中存在差异,因此处理(比如,转换)从一个标准下注册的域名标签到另一个存在差异。IDNA2003存在的过渡时期会有一定的时常。user agent应该实现IDNA2008(RFC5890)并且可能会实现UTS46或者RFC5895以加快IDNA的过渡。如果user agent没有实现IDNA2008,那它应该实现IDNA2003(RFC3490)。
7. 隐私的考虑
cookie因为允许服务器追踪用户饱受批评。例如,很多“web分析”公司使用cookie来识别用户是否回到了网站或者访问了另一个站点。虽然cookie不是服务器唯一可以用来追踪跨HTTP请求的手段,但是cookie促进了追踪,因为他们在user agent的会话之间可以一直存在,并且可以被多个host共享。
7.1. 第三方cookie
特别令人担忧的是所谓的“第三方”cookie。在渲染一个HMTL文档时,user agent经常从其他服务器请求资源(例如广告网络)。这些第三方服务器可以使用cookie来跟踪用户,尽管用户没有直接访问他们的服务器。例如,如果一个用户访问了一个带有第三方内容的网站,之后用户浏览另一个包含这个内容的网站时,第三方可以跨域两个站点追踪用户。
一些user agent限制了第三方cookie的行为。例如,其中一些user agent拒绝在向第三方的请求中发送Cookie头部。另外一些则是拒绝处理第三方请求的响应中的Set-Cookie头部。user agent在处理的第三方cookie策略方面有很多不同。本文档允许user agent在很大范围内尝试第三方cookie的策略,来满足用户对隐私和兼容性的需求。然而,本文档并不偏向于任何特别的第三方cookie策略。
禁止第三方cookie的策略,当服务器尝试绕过这个追踪用户的限制来实现他们隐私追踪时,是无效的。
尤其是,两个协作的服务器经常通过动态url而不是cookie添加识别信息,来追踪用户的时候。
7.2. 用户控制
user agent应该提供一种给用户管理存储在cookie存储中cookie的机制。例如,user agent可能让用户删除一段时间内的所有cookie或者和某个domain相关的所有cookie另外,许多user agent都包含了一个让用户检查存储在cookie store中的cookie的界面。
user agent应该提供一种可以让用户禁用cookie的机制。当cookie被禁用时,user agent禁止在发出的HTTP请求中包含Cookie头部,并且user agent处理收到的HTTP响应中的Set-Cookie头部。
一些user agent提供了阻止cookie跨session存储的选项。当这个被配置到的时候,user agent必须将所有收到的cookie当做persistent-flag设定为false进行对待。一些流行的user agent通过“匿名浏览模式”来开放这个功能。
某些user agent提供了允许用户自己写入cookie的能力。在大多数常见的场景中,这会产生大量的对话框。然而,尽管如此,某些看中隐私的用户认为这个功能很有用。
7.3. 过期时间
虽然服务器可以将cookie的过期时间设定到一个遥远的未来,但是绝大多数user agent实际上并不会将cookie保留几十年。与其选择无厘头的很长的国务时间,服务器应该,基于实际目的来选择一个合适的cookie过期时间,以提高用户的隐私性。例如,一个典型的cookie标识符应该设置成过期时间为两周比较合理。
8. 安全考虑
8.1. 概述
cookie可能有很多安全陷阱。本节概述了几个比较显著的问题。
尤其是,cookie鼓励开发者依赖Ambient Authority做认证,往往提供了被攻击的弱点,比如说跨站请求伪造(CSRF)。同时,当在cookie中存储session标识符时,开发者往往也留下了session fixation的隐患。
传输层加密,例如使用HTTPS,并不足以防御网络攻击者获得或者更改受攻击者的cookie,因为cookie协议本身有很多脆弱性(见下面的“弱机密性”和“弱完整性”)。另外,默认情况下,cookie不能从网络攻击者那儿获得完整性和机密性,甚至在使用HTTPS时。
8.2. 环境授权(Ambient Authority)
使用cookie来认证用户的服务器可能会承受安全上的脆弱性,因为一些user agent允许远程组织从user agent发送HTTP请求(例如,通过HTTP重定向或者HTML表单)。当处理这些请求时,user agent会附上这些cookie,尽管远程组织不知道cookie的内容,但是也潜在地允许远程组织利用在
没有防备性的服务器的授权。
尽管这个安全担忧有过许多名字(比如,跨站请求伪造,confused deputy),这个问题起源于将cookie作为一种环境授权(Ambient Authority)。cookie鼓励服务器的管理员将名称(用URL的形式)和授权(cookie)中分离。结果是,user agent可能会向攻击者指定的地址进行授权,可能导致服务器和客户端承受由攻击者指定的动作,因为他们曾经被用户授权。
服务器管理员可能会考虑通过将URL作为授权表,将名字(URL)和授权绑在一起,来作为取代使用cookie作为授权的手段。取代将secret存在cookie的是,这个方法将secret存在URL中,要求远程
实体自己提供授权。虽然这个方法不是万能药,审慎的运用这个原则可以获得更好的安全性。
8.3. 明文
除非以安全的途径传输(例如TLS),在Cookie和Set-Cookie字头是以明文传输的。
- 这些头部中所有传输的敏感信息都曝光给了窃听者。
- 一个怀有恶意的中间人可以更改任一方向中的头部,带来不可预知的后果。
- 一个怀有恶意的客户端可以在传输之前更改Cookie头部,带来不可预知的后果。
当传输到user agent时(就算在安全的隧道发送cookie也是),服务器应该加密并签名cookie的内容(使用任何服务器想使用的格式)。然而,加密并签名的cookie内容并没有预防攻击者将cookie从一个user agent转移到另一个,或者之后重放cookie进行攻击。
签名和加密每个cookie的内容之外,要求一个更高安全等级的服务器应该只在一个安全的隧道中使用Cookie和Set-Cookie头。当使用安全渠道的cookie时,服务器应该设定每个cookie的Secure属性。如果服务器没有设置Secure属性,由安全隧道提供的保护将会很大程度上的没有意义。
例如,考虑一个webmail服务器,将session标识符存在一个cookie钟,并且通过HTTPS访问。如果服务器没有在它的cookie上设定安全属性,一个活跃的网络攻击者将可以拦截任何由user agent发出的HTTP请求并且将其请求重定向到向HTTP上的webmail服务器。就算webmail服务器没有监听HTTP连接,user agent也会在该请求中包含cookie。这个活跃的网络攻击者拦截这些cookie,并且向服务器重放攻击,之后获取到用户的邮件内容。如果,取而代之,服务器在cookie中设定了Secure属性,
user agent将会在明文请求中包含这个cookie。
8.4. session标识符
服务器一般在cookie中存储一个nonce(或者session标识符)来代替,直接在cookie中存储session信息(可能会被攻击者获得或者重放)。当服务器收到一个拥有nonce的HTTP请求时,服务器会把nonce当做key查找和cookie相关联的状态信息。
使用session标识符限制了攻击者可以造成的危害,如果攻击者拿到了cookie的内容,因为nonce是唯一一个和服务器交互的有用信息(不像非nonce得cookie内容,其本身就是敏感的)。而且,使用单个nonce避免了攻击者将两个交互中的内容混杂起来,造成服务器不可预期的行为。
使用session标识符也不是完全没有风险。例如,服务器应该避免“session fixation”造成的易受攻击性。固化session攻击以下面三个步骤进行。首先,攻击者将session标识符从他的user agent中
移植到受害者的user agent中。第二,受害者使用这个session和服务器交互,可能会用用户信息填充这个session标识符。第三,攻击者直接使用这个session标识符和服务器交互,可能会获得授权信息或者机密信息。
8.5. 弱机密性
cookie没有提供端口隔离。如果一个cookie在一个端口上是可读的,那么这个cookie对另一个运行在同一个服务器上不同端口的服务来说也是可读的。如果一个cookie在一个端口上是可写的,那么这个cookie对另一个运行在同一个服务器上不同端口的服务来说也是可写的。出于这个原因,服务器不应该在具有相同host的不同端口上运行相互不信任的服务,并且用cookie来存储安全敏感信息。
cookie没有提供协议带来的隔离性。虽然绝大多数通常都使用http和HTTPS协议,给定host的cookie也可能被其他协议获取到,例如ftp和gopher。虽然缺少协议隔离性,对从非HTTP的API获取cookie的权限是显著的,但是实际上由协议导致的隔离性缺失表现在了他们需要自己处理cookie(例如,考虑通过HTTP取回一个使用gopher协议的URI)。
cookie往往也没有提供基于path的隔离性。虽然网络层的协议并没有将存储在一个path的协议发到另一个,但是某些user agent通过非HTTP的api暴露了这些cookie,例如HTML的document.cookie API。因为其中的某些user agent(例如,浏览器)并没有将从不同path获得的资源隔离起来,从一个path取回的某个资源也可能可以拿到存储到另一个path的cookie。
8.6. 弱完整性
cookie没有为兄弟域名(极其子域名)提供完整性保障。例如,考虑foo.example.com和bar.example.com。foo.example.com服务器可以设置一个Domain属性为"example.com"的cookie(可能覆盖一个现有的由bar.example.com设置的"example.com"的cookie)。并且user agent会在想bar.example.com的HTTP请求中包含这个cookie。在最坏的情况下,bar.example.com将不能把这个cookie和一个自己设定cookie区分开来。foo.example.com服务器可能会利用这个能力来发起对bar.example.com的攻击。
尽管Set-Cookie头支持Path属性,path属性也没有提供任何完整性保证,因为user agent将会接受一个Set-Cookie头部的任意路径。例如,一个对 http://example.com/foo/bar 的HTTP响应,可以设置一个Path属性为"/qux"的cookie。因此,服务器不应该在相同host上的不同path运行两个相互不信任的服务,并且使用cookie来存储安全敏感信息。
某个活动的网络攻击者也可以向发送到 https://example.com/ 的请求中的Cookie头部注入cookie,方法是仿造一个 http://example.com/ 的响应并且注入一个Set-Cookie头。位于example.com的HTTPS服务器将不能分辨gaicooie是否是他自己在HTTPS响应中设置的cookie。一个活跃的网络攻击者可能会通过这个机制来攻击example.com,即使example.com只使用了HTTPS。
译者注:例如,用户直接敲了一个HTTP站点,www.baidu.com 在重完成定向到HTTPS之前,中间人可以伪造跳转,比如返回一个302跳转到 https://www.baidu.com 同时中间人设定一个 cookie。
服务器可以通过加密和签名他们的 cookie 来缓和这些攻击。然而,使用密码学并没有完全缓解这个问题,因为一个攻击者可以重放TA从真实的 example.com 服务器中的 sessuib 获取到的 cookie,以导致不可预期的后果。
最后,一个攻击者可以通过存储大量 cookie 来强迫 user agent 删除掉 cookie。一旦 user agent 达到了它的存储限制,user agent 会被迫驱除掉某些 cookie。服务器不应该依赖 user agent 对 cookie 的保留。
8.7. 依赖 DNS
cookie 依赖 DNS 系统来提供安全性。如果 DNS 部分或全部受损,cookie 协议可能会无法提供应用所要求的安全属性
9. IANA 考虑
永久的消息头部注册(RFC 3864)已经被下列登记项目更新。
9.1. Cookie
- 头部名称: Cookie
- 应用协议: http
- 状态: 标准
- 作者/变化控制者: IETF
- 标准文档: 本文档(5.4节)
9.2. Set-Cookie
- 头部名称: Set-Cookie
- 应用协议: http
- 状态: 标准
- 作者/变化控制者: IETF
- 标准文档: 本文档(5.2节)
9.3. Cookie2
- 头部名称: Cookie2
- 应用协议: http
- 状态: 已淘汰
- 作者/变化控制者: IETF
- 标准文档: RFC2965
9.4. Set-Cookie2
- 头部名称: Set-Cookie2
- 应用协议: http
- 状态: 已淘汰
- 作者/变化控制者: IETF
- 标准文档: RFC2965
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: HTTP 中的缓存介绍
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论