# Cookie,document.cookie Cookie 是直接存储在浏览器中的一小串数据。它们是 HTTP 协议的一部分,由 [RFC 6265](https://tools.ietf.org/html/rfc6265) 规范定义。 Cookie 通常是由 Web 服务器使用响应 `Set-Cookie` HTTP-header 设置的。然后浏览器使用 `Cookie` HTTP-header 将它们自动添加到(几乎)每个对相同域的请求中。 最常见的用处之一就是身份验证: 1. 登录后,服务器在响应中使用 `Set-Cookie` HTTP-header 来设置具有唯一“会话标识符(session identifier)”的 cookie。 2. 下次当请求被发送到同一个域时,浏览器会使用 `Cookie` HTTP-header 通过网络发送 cookie。 3. 所以服务器知道是谁发起了请求。 我们还可以使用 `document.cookie` 属性从浏览器访问 cookie。 关于 cookie 及其选项,有很多棘手的事情。在本章中,我们将详细介绍它们。 ## 从 document.cookie 中读取 ```online 你的浏览器是否存储了本网站的任何 cookie?让我们来看看: ``` ```offline 假设你在一个网站上,则可以看到来自该网站的 cookie,像这样: ``` ```js run // 在 javascript.info,我们使用谷歌分析来进行统计, // 所以应该存在一些 cookie alert( document.cookie ); // cookie1=value1; cookie2=value2;... ``` `document.cookie` 的值由 `name=value` 对组成,以 `; ` 分隔。每一个都是独立的 cookie。 为了找到一个特定的 cookie,我们可以以 `; ` 作为分隔,将 `document.cookie` 分开,然后找到对应的名字。我们可以使用正则表达式或者数组函数来实现。 我们把这个留给读者当作练习。此外,在本章的最后,你可以找到一些操作 cookie 的辅助函数。 ## 写入 document.cookie 我们可以写入 `document.cookie`。但这不是一个数据属性,它是一个 [访问器(getter/setter)](info:property-accessors)。对其的赋值操作会被特殊处理。 **对 `document.cookie` 的写入操作只会更新其中提到的 cookie,而不会涉及其他 cookie。** 例如,此调用设置了一个名称为 `user` 且值为 `John` 的 cookie: ```js run document.cookie = "user=John"; // 只会更新名称为 user 的 cookie alert(document.cookie); // 展示所有 cookie ``` 如果你运行了上面这段代码,你会看到多个 cookie。这是因为 `document.cookie=` 操作不是重写整所有 cookie。它只设置代码中提到的 cookie `user`。 从技术上讲,cookie 的名称和值可以是任何字符。为了保持有效的格式,它们应该使用内建的 `encodeURIComponent` 函数对其进行转义: ```js run // 特殊字符(空格),需要编码 let name = "my name"; let value = "John Smith" // 将 cookie 编码为 my%20name=John%20Smith document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value); alert(document.cookie); // ...; my%20name=John%20Smith ``` ```warn header="限制" 存在一些限制: - `encodeURIComponent` 编码后的 `name=value` 对,大小不能超过 4KB。因此,我们不能在一个 cookie 中保存大的东西。 - 每个域的 cookie 总数不得超过 20+ 左右,具体限制取决于浏览器。 ``` Cookie 有几个选项,其中很多都很重要,应该设置它。 选项被列在 `key=value` 之后,以 `;` 分隔,像这样: ```js run document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" ``` ## path - **`path=/mypath`** url 路径前缀必须是绝对路径。它使得该路径下的页面可以访问该 cookie。默认为当前路径。 如果一个 cookie 带有 `path=/admin` 设置,那么该 cookie 在 `/admin` 和 `/admin/something` 下都是可见的,但是在 `/home` 或 `/adminpage` 下不可见。 通常,我们应该将 `path` 设置为根目录:`path=/`,以使 cookie 对此网站的所有页面可见。 ## domain - **`domain=site.com`** domain 控制了可访问 cookie 的域。但是在实际中,有一些限制。我们无法设置任何域。 **无法从另一个二级域访问 cookie,因此 `other.com` 永远不会收到在 `site.com` 设置的 cookie。** 这是一项安全限制,为了允许我们将敏感数据存储在应该仅在一个站点上可用的 cookie 中。 默认情况下,cookie 只有在设置的域下才能被访问到。 请注意,默认情况下,cookie 也不会共享给子域,例如 `forum.site.com`。 ```js // 如果我们在 site.com 网站上设置了 cookie…… document.cookie = "user=John" // ……在 forum.site.com 域下我们无法访问它 alert(document.cookie); // 没有 user ``` ……但这是可以设置的。如果我们想允许像 `forum.site.com` 这样的子域在 `site.com` 上设置 cookie,也是可以实现的。 为此,当在 `site.com` 设置 cookie 时,我们应该明确地将 `domain` 选项设置为根域:`domain=site.com`。那么,所有子域都可以访问到这样的 cookie。 例如: ```js // 在 site.com // 使 cookie 可以被在任何子域 *.site.com 访问: document.cookie = "user=John; *!*domain=site.com*/!*" // 之后 // 在 forum.site.com alert(document.cookie); // 有 cookie user=John ``` 出于历史原因,`domain=.site.com`(`site.com` 前面有一个点符号)也以相同的方式工作,允许从子域访问 cookie。这是一个旧的表示方式,如果我们需要支持非常旧的浏览器,那么应该使用它。 总结一下,通过 `domain` 选项的设置,可以实现允许在子域访问 cookie。 ## expires,max-age 默认情况下,如果一个 cookie 这两个参数都没有设置,那么在关闭浏览器之后,它就会消失。此类 cookie 被称为 "session cookie”。 为了让 cookie 在浏览器关闭后仍然存在,我们可以设置 `expires` 或 `max-age` 选项中的一个。 - **`expires=Tue, 19 Jan 2038 03:14:07 GMT`** cookie 的过期时间定义了浏览器会自动清除该 cookie 的时间。 日期必须完全采用 GMT 时区的这种格式。我们可以使用 `date.toUTCString` 来获取它。例如,我们可以将 cookie 设置为 1 天后过期。 ```js // 当前时间 +1 天 let date = new Date(Date.now() + 86400e3); date = date.toUTCString(); document.cookie = "user=John; expires=" + date; ``` 如果我们将 `expires` 设置为过去的时间,则 cookie 会被删除。 - **`max-age=3600`** 它是 `expires` 的替代选项,指明了 cookie 的过期时间距离当前时间的秒数。 如果将其设置为 0 或负数,则 cookie 会被删除: ```js // cookie 会在一小时后失效 document.cookie = "user=John; max-age=3600"; // 删除 cookie(让它立即过期) document.cookie = "user=John; max-age=0"; ``` ## secure - **`secure`** Cookie 应只能被通过 HTTPS 传输。 **默认情况下,如果我们在 `http://site.com` 上设置了 cookie,那么该 cookie 也会出现在 `https://site.com` 上,反之亦然。** 也就是说,cookie 是基于域的,它们不区分协议。 使用此选项,如果一个 cookie 是通过 `https://site.com` 设置的,那么它不会在相同域的 HTTP 环境下出现,例如 `http://site.com`。所以,如果一个 cookie 包含绝不应该通过未加密的 HTTP 协议发送的敏感内容,那么就应该设置 `secure` 标识。 ```js // 假设我们现在在 HTTPS 环境下 // 设置 cookie secure(只在 HTTPS 环境下可访问) document.cookie = "user=John; secure"; ``` ## samesite 这是另外一个关于安全的特性。它旨在防止 XSRF(跨网站请求伪造)攻击。 为了了解它是如何工作的,以及何时有用,让我们看一下 XSRF 攻击。 ### XSRF 攻击 想象一下,你登录了 `bank.com` 网站。此时:你有了来自该网站的身份验证 cookie。你的浏览器会在每次请求时将其发送到 `bank.com`,以便识别你,并执行所有敏感的财务上的操作。 现在,在另外一个窗口中浏览网页时,你不小心访问了另一个网站 `evil.com`。该网站具有向 `bank.com` 网站提交一个具有启动与黑客账户交易的字段的表单 `