cookie机制
Cookie 的实现机制
Cookie 是由客户端保存的小型文本文件,其内容为一系列的键值对。 Cookie是由HTTP服务器设置的,保存在浏览器中, 在用户访问其他页面时,会在HTTP请求中附上该服务器之前设置的Cookie。 Cookie的实现标准定义在 RFC2109: HTTP State Management Mechanism 中。 那么Cookie是怎样工作的呢?下面给出整个Cookie的传递流程:
-
浏览器向某个URL发起HTTP请求(可以是任何请求,比如GET一个页面、POST一个登录表单等)
-
对应的服务器收到该HTTP请求,并计算应当返回给浏览器的HTTP响应。
HTTP响应包括请求头和请求体两部分,可以参见:读 HTTP 协议。
-
在响应头加入
Set-Cookie
字段,它的值是要设置的Cookie。在 RFC2109 6.3 Implementation Limits 中提到: UserAgent(浏览器就是一种用户代理)至少应支持300项Cookie, 每项至少应支持到4096字节,每个域名至少支持20项Cookie。
-
浏览器收到来自服务器的HTTP响应。
-
浏览器在响应头中发现
Set-Cookie
字段,就会将该字段的值保存在内存或者硬盘中。Set-Cookie
字段的值可以是很多项Cookie,每一项都可以指定过期时间Expires
。 默认的过期时间是用户关闭浏览器时。 -
浏览器下次给该服务器发送HTTP请求时, 会将服务器设置的Cookie附加在HTTP请求的头字段
Cookie
中。浏览器可以存储多个域名下的Cookie,但只发送当前请求的域名曾经指定的Cookie, 这个域名也可以在
Set-Cookie
字段中指定)。 -
服务器收到这个HTTP请求,发现请求头中有
Cookie
字段, 便知道之前就和这个用户打过交道了。 -
过期的Cookie会被浏览器删除。
总之,服务器通过Set-Cookie
响应头字段来指示浏览器保存Cookie, 浏览器通过Cookie
请求头字段来告诉服务器之前的状态。 Cookie中包含若干个键值对,每个键值对可以设置过期时间。
cookie 的属性选项
每个cookie
都有一定的属性,如什么时候失效,要发送到哪个域名,哪个路径等等。这些属性是通过cookie
选项来设置的,cookie
选项包括:expires
、domain
、path
、secure
、HttpOnly
。在设置任一个cookie
时都可以设置相关的这些属性,当然也可以不设置,这时会使用这些属性的默认值。在设置这些属性时,属性之间由一个**分号
和一个空格
**隔开。代码示例如下:
1 | "key=name; expires=Thu, 25 Feb 2016 04:18:00 GMT; domain=ppsc.sankuai.com; path=/; secure; HttpOnly" |
expires
expires
选项用来设置“cookie 什么时间内有效”。expires
其实是cookie
失效日期,expires
必须是 GMT
格式的时间(可以通过 new Date().toGMTString()
或者 new Date().toUTCString()
来获得)。
如expires=Thu, 25 Feb 2016 04:18:00 GMT
表示cookie
讲在2016年2月25日4:18分之后失效,对于失效的cookie
浏览器会清空。如果没有设置该选项,则默认有效期为session
,即会话cookie
。这种cookie
在浏览器关闭后就没有了。
expires 是 http/1.0协议中的选项,在新的http/1.1协议中
expires已经由
max-age 选项代替,两者的作用都是限制cookie 的有效时间。
expires的值是一个时间点(
cookie失效时刻= expires),而
max-age 的值是一个以
秒为单位时间段(
cookie失效时刻= 创建时刻+ max-age)。 另外,
max-age的默认值是
-1(即有效期为
session );若
max-age有三种可能值:负数、0、正数。负数:有效期
session;
0:删除
cookie;正数:有效期为
创建时刻+ max-age
domain 和 path
domain
是域名,path
是路径,两者加起来就构成了 URL,domain
和path
一起来限制 cookie 能被哪些 URL 访问。
一句话概括:某cookie的 domain
为“baidu.com”, path
为“/ ”,若请求的URL(URL 可以是js/html/img/css资源请求,但不包括 XHR 请求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路径是“/ ”或子路径“/home”、“/home/login”,则浏览器会将此 cookie 添加到该请求的 cookie 头部中。
所以domain
和path
2个选项共同决定了cookie
何时被浏览器自动添加到请求头部中发送出去。如果没有设置这两个选项,则会使用默认值。domain
的默认值为设置该cookie
的网页所在的域名,path
默认值为设置该cookie
的网页所在的目录。
特别说明1:
发生跨域xhr请求时,即使请求URL的域名和路径都满足 cookie 的 domain 和 path,默认情况下cookie也不会自动被添加到请求头部中。若想知道原因请阅读本文最后一节)特别说明2:
domain是可以设置为页面本身的域名(本域),或页面本身域名的父域,但不能是公共后缀 public suffix。举例说明下:如果页面域名为 www.baidu.com, domain可以设置为“www.baidu.com”,也可以设置为“baidu.com”,但不能设置为“.com”或“com”。
secure
secure
选项用来设置cookie
只在确保安全的请求中才会发送。当请求是HTTPS
或者其他安全协议时,包含 secure
选项的 cookie
才能被发送至服务器。
默认情况下,cookie
不会带secure
选项(即为空)。所以默认情况下,不管是HTTPS
协议还是HTTP
协议的请求,cookie
都会被发送至服务端。但要注意一点,secure
选项只是限定了在安全情况下才可以传输给服务端,但并不代表你不能看到这个 cookie。
下面我们设置一个 secure
类型的 cookie:
1 | document.cookie = "name=huang; secure"; |
之后你就能在控制台中看到这个 cookie 了,如下图所示:
这里有个坑需要注意下:
如果想在客户端即网页中通过 js 去设置secure
类型的 cookie,必须保证网页是https
协议的。在http
协议的网页中是无法设置secure
类型cookie的。
httpOnly
这个选项用来设置cookie
是否能通过 js
去访问。默认情况下,cookie
不会带httpOnly
选项(即为空),所以默认情况下,客户端是可以通过js
代码去访问(包括读取、修改、删除等)这个cookie
的。当cookie
带httpOnly
选项时,客户端则无法通过js
代码去访问(包括读取、修改、删除等)这个cookie
。
在客户端是不能通过js
代码去设置一个httpOnly
类型的cookie
的,这种类型的cookie
只能通过服务端来设置。
那我们在页面中怎么知道哪些cookie
是httpOnly
类型的呢?看下图:
凡是httpOnly
类型的cookie
,其 HTTP 一列都会打上√,如上图中的PA_VTIME
。你通过document.cookie
是不能获取的,也不能修改PA_VTIME
的。
——
httpOnly
与安全从上面介绍中,大家是否会有这样的疑问:为什么我们要限制客户端去访问
cookie
?其实这样做是为了保障安全。试想:如果任何 cookie 都能被客户端通过
document.cookie
获取会发生什么可怕的事情。当我们的网页遭受了 XSS 攻击,有一段恶意的script
脚本插到了网页中。这段script
脚本做的事情是:通过document.cookie
读取了用户身份验证相关的 cookie,并将这些 cookie 发送到了攻击者的服务器。攻击者轻而易举就拿到了用户身份验证信息,于是就可以摇摇大摆地冒充此用户访问你的服务器了(因为攻击者有合法的用户身份验证信息,所以会通过你服务器的验证)。