聊一聊 cookie

我们不搞一开始就一大堆理论知识介绍,怕把人讲懵了...... 我们换一个思惟方式——"从现象看本质",先说说咱们看到了什么,再从看到的现象中提出问题,最后深刻寻找答案。javascript

咱们看到的 cookie

我本身建立了一个网站,网址为http://ppsc.sankuai.com。在这个网页中我设置了几个cookieJSSESSIONIDPA_VTIMEskmtutctestcss

在 chrome 浏览器中打开这个网站,进入开发者模式,点击Resources栏 -> 选择cookies,咱们会看到以下图所示的界面:html

图片描述

解释一下:左边栏Cookies下方会列举当前网页中设置过cookie的域都有哪些。上图中只有一个域,即“ppsc.sankuai.com”。而右侧区域显示的就是某个域下具体的 cookie 列表,对应上图就是“ppsc.sankuai.com”域下设置的4个cookie前端

在这个网页中我往http://ppsc.sankuai.com/getList接口发了一个 Ajax 请求,request header以下图所示:java

图片描述

从上图中咱们会看到request header中自动添加了Cookie字段(我并无手动添加这个字段哦~),Cookie字段的值其实就是我设置的那4个 cookie。这个请求最终会发送到http://ppsc.sankuai.com这个服务器上,这个服务器就能从接收到的request header中提取那4个cookieweb

上面两张图展现了cookie的基本通讯流程:设置cookie => cookie被自动添加到request header中 => 服务端接收到cookie。这个流程中有几个问题须要好好研究:ajax

  1. 什么样的数据适合放在cookie中?chrome

  2. cookie是怎么设置的?segmentfault

  3. cookie为何会自动加到request header中?api

  4. cookie怎么增删查改?

咱们要带着这几个问题继续往下阅读。

cookie 是怎么工做的?

首先必须明确一点,存储cookie是浏览器提供的功能。cookie 实际上是存储在浏览器中的纯文本,浏览器的安装目录下会专门有一个 cookie 文件夹来存放各个域下设置的cookie

当网页要发http请求时,浏览器会先检查是否有相应的cookie,有则自动添加在request header中的cookie字段中。这些是浏览器自动帮咱们作的,并且每一次http请求浏览器都会自动帮咱们作。这个特色很重要,由于这关系到“什么样的数据适合存储在cookie中”。

存储在cookie中的数据,每次都会被浏览器自动放在http请求中,若是这些数据并非每一个请求都须要发给服务端的数据,浏览器这设置自动处理无疑增长了网络开销;但若是这些数据是每一个请求都须要发给服务端的数据(好比身份认证信息),浏览器这设置自动处理就大大免去了重复添加操做。因此对于那设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其余类型的数据就不适合了。

但在 localStorage 出现以前,cookie被滥用当作了存储工具。什么数据都放在cookie中,即便这些数据只在页面中使用而不须要随请求传送到服务端。固然cookie标准仍是作了一些限制的:每一个域名下的cookie 的大小最大为4KB,每一个域名下的cookie数量最多为20个(但不少浏览器厂商在具体实现时支持大于20个)。

cookie 的格式

document.cookie

JS 原生的 API提供了获取cookie的方法:document.cookie(注意,这个方法只能获取非 HttpOnly 类型的cookie)。在 console 中执行这段代码能够看到结果以下图:

图片描述

打印出的结果是一个字符串类型,由于cookie自己就是存储在浏览器中的字符串。但这个字符串是有格式的,由键值对 key=value构成,键值对之间由一个分号和一个空格隔开。

cookie 的属性选项

每一个cookie都有必定的属性,如何时失效,要发送到哪一个域名,哪一个路径等等。这些属性是经过cookie选项来设置的,cookie选项包括:expiresdomainpathsecureHttpOnly。在设置任一个cookie时均可以设置相关的这些属性,固然也能够不设置,这时会使用这些属性的默认值。在设置这些属性时,属性之间由一个分号和一个空格隔开。代码示例以下:

"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、正数。负数:有效期session0:删除cookie;正数:有效期为建立时刻+ max-age

domain 和 path

domain是域名,path是路径,二者加起来就构成了 URL,domainpath一块儿来限制 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 头部中。

因此domainpath2个选项共同决定了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:

document.cookie = "name=huang; secure";复制代码

以后你就能在控制台中看到这个 cookie 了,以下图所示:

图片描述

这里有个坑须要注意下:
若是想在客户端即网页中经过 js 去设置secure类型的 cookie,必须保证网页是https协议的。在http协议的网页中是没法设置secure类型cookie的。

httpOnly

这个选项用来设置cookie是否能经过 js 去访问。默认状况下,cookie不会带httpOnly选项(即为空),因此默认状况下,客户端是能够经过js代码去访问(包括读取、修改、删除等)这个cookie的。当cookiehttpOnly选项时,客户端则没法经过js代码去访问(包括读取、修改、删除等)这个cookie

在客户端是不能经过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能经过服务端来设置。

那咱们在页面中怎么知道哪些cookiehttpOnly类型的呢?看下图:

图片描述

凡是httpOnly类型的cookie,其 HTTP 一列都会打上√,如上图中的PA_VTIME。你经过document.cookie是不能获取的,也不能修改PA_VTIME的。

——httpOnly与安全

从上面介绍中,你们是否会有这样的疑问:为何咱们要限制客户端去访问cookie?其实这样作是为了保障安全。

试想:若是任何 cookie 都能被客户端经过document.cookie获取会发生什么可怕的事情。当咱们的网页遭受了 XSS 攻击,有一段恶意的script脚本插到了网页中。这段script脚本作的事情是:经过document.cookie读取了用户身份验证相关的 cookie,并将这些 cookie 发送到了攻击者的服务器。攻击者垂手可得就拿到了用户身份验证信息,因而就能够摇摇大摆地冒充此用户访问你的服务器了(由于攻击者有合法的用户身份验证信息,因此会经过你服务器的验证)。

如何设置 cookie?

知道了cookie的格式,cookie的属性选项,接下来咱们就能够设置cookie了。首先得明确一点:cookie既能够由服务端来设置,也能够由客户端来设置。

服务端设置 cookie

无论你是请求一个资源文件(如 html/js/css/图片),仍是发送一个ajax请求,服务端都会返回response。而response header中有一项叫set-cookie,是服务端专门用来设置cookie的。以下图所示,服务端返回的response header中有5个set-cookie字段,每一个字段对应一个cookie(注意不能将多个cookie放在一个set-cookie字段中),set-cookie字段的值就是普通的字符串,每一个cookie还设置了相关属性选项。

图片描述

注意:

  • 一个set-Cookie字段只能设置一个cookie,当你要想设置多个 cookie,须要添加一样多的set-Cookie字段。

  • 服务端能够设置cookie 的全部选项:expiresdomainpathsecureHttpOnly

客户端设置 cookie

在网页即客户端中咱们也能够经过js代码来设置cookie。如我当前打开的网址为http://dxw.st.sankuai.com/mp/,在控制台中咱们执行了下面代码:

document.cookie = "name=Jonh; ";复制代码

查看浏览器 cookie 面板以下图所示,cookie确实设置成功了,并且属性选项 domainpathexpires都用了默认值。

图片描述

再执行下面代码:

document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";复制代码

查看浏览器cookie 面板,以下图所示,新的cookie设置成功了,并且属性选项 domainpathexpires都变成了设定的值。

图片描述

注意:

  • 客户端能够设置cookie 的下列选项:expiresdomainpathsecure(有条件:只有在https协议的网页中,客户端设置secure类型的 cookie 才能成功),但没法设置HttpOnly选项。

用 js 如何设置多个 cookie

当要设置多个cookie时, js 代码很天然地咱们会这么写:

document.cookie = "name=Jonh; age=12; class=111";复制代码

但你会发现这样写只是添加了第一个cookie“name=John”,后面的全部cookie都没有添加成功。因此最简单的设置多个cookie的方法就在重复执行document.cookie = "key=name",以下:

document.cookie = "name=Jonh";
document.cookie = "age=12";
document.cookie = "class=111";复制代码

如何修改、删除

修改 cookie

要想修改一个cookie,只须要从新赋值就行,旧的值会被新的值覆盖。但要注意一点,在设置新cookie时,path/domain这几个选项必定要旧cookie 保持同样。不然不会修改旧值,而是添加了一个新的 cookie。

删除 cookie

删除一个cookie 也挺简单,也是从新赋值,只要将这个新cookie的expires 选项设置为一个过去的时间点就好了。但一样要注意,path/domain/这几个选项必定要旧cookie 保持同样。

cookie 编码

cookie实际上是个字符串,但这个字符串中逗号、分号、空格被当作了特殊符号。因此当cookie的 key 和 value 中含有这3个特殊字符时,须要对其进行额外编码,通常会用escape进行编码,读取时用unescape进行解码;固然也能够用encodeURIComponent/decodeURIComponent或者encodeURI/decodeURI三者的区别能够参考这篇文章)。

var key = escape("name;value");
var value = escape("this is a value contain , and ;");
document.cookie= key + "=" + value + "; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";复制代码

跨域请求中 cookie

以前在介绍 XHR 的一篇文章里面提过:默认状况下,在发生跨域时,cookie 做为一种 credential 信息是不会被传送到服务端的。必需要进行额外设置才能够。具体缘由和如何设置能够参考个人这篇文章:你真的会使用XMLHttpRequest吗?

另外,关于跨域资源共享 CORS极力推荐你们阅读阮一峰老师的这篇 跨域资源共享 CORS 详解

其余补充

  1. 何时 cookie 会被覆盖:name/domain/path 这3个字段都相同的时候;

  2. 关于domain的补充说明(参考1/参考2):

    1. 若是显式设置了 domain,则设置成什么,浏览器就存成什么;但若是没有显式设置,则浏览器会自动取 url 的 host 做为 domain 值;

    2. 新的规范中,显式设置 domain 时,若是 value 最前面带点,则浏览器处理时会将这个点去掉,因此最后浏览器存的就是没有点的(注意:但目前大多数浏览器并未所有这么实现)

    3. 前面带点‘.’和不带点‘.’有啥区别:

      • 带点:任何 subdomain 均可以访问,包括父 domain

      • 不带点:只有彻底同样的域名才能访问,subdomain 不能(但在 IE 下比较特殊,它支持 subdomain 访问)

总结

我们今天就聊到这里,如有不对之处欢迎各位指正~~
最后附上一些参考资料:

  1. www.quirksmode.org/js/...

  2. www.tutorialspoint.com...

  3. www.allaboutcookies.or...

  4. bubkoo.com/2014/04/21/…

ruoyiqing 发布于 WEB前端路上踩过的坑儿
关注专栏
相关文章
相关标签/搜索