ZhichengChen - 一文了解 web 开发中的 cookie
你是否曾经想过为什么登录站点后关闭浏览器也可以保留登录状态,以及在登录前就可以将商品添加到购物车中?
无论你是否了解,cookie 随处可见,它们彻底改变了我们使用网络的方式。
在本文中,我们将介绍 cookie 的历史、它们的工作方式、如何在 JavaScript 中使用它们以及需要熟知的一些安全隐患。
cookies 简史
HTTP 超文本传输协议是一种无状态协议。根据 Wikipedia,它之所以无状态,是因为它“不需要 HTTP 服务器在不同请求期间保留用户的信息或状态”。
今天,网站服务依然是如此实现的 – 输入浏览器的URL,浏览器向某处的服务器发出请求,然后服务器返回文件以呈现页面,然后关闭连接。
现在,假设需要登录网站以查看某些内容,例如使用 LinkedIn。该过程与上述过程基本相同,会看到一个表单,用于输入电子邮件地址和密码。
输入相关信息,然后浏览器将其发送到服务器。服务器检查登录信息,如果一切顺利,它将把呈现页面所需的数据发送回浏览器。
但是,如果 LinkedIn 是无状态的,一旦跳转到另一个页面,服务器将不记得你刚刚已经登录过。它将要求你再次输入电子邮件地址和密码,进行检查后在发送数据以渲染新页面。
那会非常令人沮丧,不是吗?许多开发人员也这样认为,并找到了很多在 Web 上创建有状态会话的方法。
cookie 的发明
90 年代初期,Netscape 的开发人员 Lou Montoulli 遇到了一个问题 – 他正在为另一家公司 MCI 开发一个在线商店,该商店会在服务器中存储每个客户购物车中的商品。这意味着人们必须首先创建一个帐户,创建账户需要花费一些时间,存储每个用户购物车的内容也会占用大量存储空间。
MCI 要求将这些数据存储在客户自己的计算机上。他们还希望客户在没有登录时也能添加商品到购物车。
为了解决这个问题,Lou 提出了一个在程序员中已经广为人知的想法:魔力 cookie。
魔力 cookie 或 cookie,是可以在两个计算机程序之间传递的少量数据。它们之所以“魔幻”,是因为 cookie 中的数据通常是随机密钥或令牌,并且实际只对使用它的软件才有意义。
Lou 采用了魔力 cookie 概念,并将其应用于在线商店,后来又应用于浏览器。
现在了解了 cookie 的历史,来看一下如何使用 cookie 在网络上创建有状态会话。
cookies 如何工作
和 cookie 有点像的一个场景,就是你在游乐园中获得的腕带。
当登录网站时,过程就像进入游乐园一样。首先,要买票,然后进入公园时,工作人员会检票并给你一条腕带。
这和登录的方式一样 - 服务器检查你的用户名和密码,创建并存储会话,生成唯一的会话 ID,然后发送回带有该会话 ID 的 cookie。
(请注意,会话 ID 不是你的密码,它是完全独立的,即时生成的。密码处理和身份验证不在本文的讨论范围之内,可以在这里了解更多。)
在游乐园中,可以通过出示腕带来消费项目。
同样,当你向登录的网站发出请求时,浏览器会将带有会话 ID 的 cookie 发送回服务器。服务器使用你的会话 ID 检查会话,然后返回请求的数据。
最后,一旦离开游乐园,你的腕带将不再起作用 - 你无法使用它重新回到公园参与游乐活动。
登出网站也一样。浏览器将带有 cookie 的注销请求发送到服务端,服务端删除 session,并告知浏览器删除具有相应 session id 的 cookie。
如果想回到游乐园,则必须购买另一张门票并获得另一个腕带。同样,如果想继续使用网站,就必须重新登录。
这是如何使用 cookie 来记录登录网站状态的简单示例。它还可以用来记录设置的深色主题模式,以及跟踪你在网站上的其它行为等。
怎样使用 cookie
现在已经了解了 cookie 的历史以及它们的作用,接下来来看一下 cookie 的一些局限,然后用实例演示一下。
cookie 的限制
与浏览器中更常用的存储数据方案(例如,localStorage 或 sessionStorage)相比,cookie 有很多局限性。 这是 cookie 和其它技术对比的摘要:
COOKIES | LOCAL STORAGE | SESSION STORAGE | |
---|---|---|---|
容量 | 4KB | 10MB | 5MB |
可访问 | 任何窗口 | 任何窗口 | 相同标签页 |
过期 | 手动设置 | 永不过期 | 标签页关闭时 |
存储位置 | 浏览器和服务端 | 仅浏览器 | 仅浏览器 |
通过请求发送 | 是 | 否 | 否 |
cookies 是一种古老的技术,并且容量非常有限。不过,可以使用它们做很多事情。 它们的体积小巧,浏览器可以轻松地在每个请求中附带 cookie 发送到服务器。
还值得一提的是,出于安全原因,浏览器仅允许 cookie 在一个域名中可见。
因此,如果你通过 ally.com 登录银行,则 cookie 将仅在该域名及其子域名内起作用。例如,你的 ally.com 的 cookie 可以在 ally.com,ally.com/about 和域名 www.ally.com 上可见,而在 axos.com 上不可见。
这意味着,即使你同时拥有两个帐户并在 ally.com 和 axos.com 上都进行了登录,这些站点也将无法读取彼此的 cookie。
重要的是要记住,你的 cookie 是随浏览器中的请求一起发送的。这非常方便,但是存在一些严重的安全隐患,后面会详述。
最后,如果本文你只能记住一个知识点,那就请记住,cookie 是公开读取和发送的,切勿在其中存储诸如密码之类的敏感信息。
怎样在 JavaScript 中设置 cookie
cookies 实际上只是带有键/值对的字符串。尽管可能更多在后端使用 cookie,但也需要客户端设置 cookie。
以下是在 vanilla JavaScript 中设置 ocokie 的方法:
document.cookie = 'dark_mode=true'
然后,当打开开发者控制台时,单击“Application(应用程序)”,然后在站点的“Cookies”下,将看到刚添加的 cookie:
如果仔细查看 cookie,会发现它的过期日期设置为 Session。这意味着,当关闭标签页/浏览器时,cookie 将被销毁。
这可能是某些场景的预期行为,例如 cookie 存有付款信息的在线商店。
但是,如果希望 cookie 的存在时间更久,需要设置一个有效期。
如何在 JavaScript 中设置 cookie 的过期时间
要设置过期时间,只需设置 cookie 的值,然后添加带有日期配置的 expires 属性,该日期一般设置为未来的某个时间:
document.cookie = 'dark_mode=true; expires=Fri, 26 Feb 2021 00:00:00 GMT' // expires 1 week from now
JavaScript Date 对象可以很方便的生成日期。 可以在此处阅读有关 Date 对象的更多信息。
或者,也可以将 max-age 属性与你希望 cookie 生效的秒数结合使用:
document.cookie = 'dark_mode=true; max-age=604800'; // expires 1 week from now
如何在 JavaScript 中更新 cookie
cookie 更新很方便,和是否具有有效期无关。
只需更改 cookie 的值,浏览器就会自动配置:
document.cookie = "dark_mode=false; max-age=604800"; // expires 1 week from now
如何在 JavaScript 中设置 cookie 的路径
有时,只希望 cookie 在网站的某些部分生效。如何操作取决于网站的设置方式,一种方法是使用 path 属性。
配置 cookie 的方法如下:使 cookie 仅在 /about 的 about 页面上起作用:
document.cookie = 'dark_mode=true; path=/about';
现在,cookie 仅能在 /about 和其他子目录(例如 /about/team)上生效,而不能在/blog 上生效。
然后,当访问 about 页面并查看 cookie 时,如下:
如何在 JavaScript 中删除 cookie
要在 JavaScript 中删除 cookie,只需将 expires 属性设置为已过的日期即可:
document.cookie = 'dark_mode=true; expires=Sun, 14 Feb 2021 00:00:00 GMT'; // 1 week earlier
也可以使用 max-age 并将其设置为负值:
document.cookie = 'dark_mode=true; max-age=-60'; // 1 minute earlier
然后,查看 cookie,会发现它已经被删除:
这应该是在 vanilla JS 中使用 cookie 所需了解的比较全面的知识。
我们介绍的所有内容都能够满足常见的需求,如果对 cookie 的使用比较频繁,可以考虑类库 JavaScript Cookie 或 Cookie Parser。
cookie 的安全性问题
通常,cookie 在正确配置的前提下非常安全。浏览器有很多内置的限制,我们之前已经介绍了这些限制,部分原因是该技术的年代久远,这反而提高了其安全性。
尽管如此,攻击者还是有一些办法窃取你的 cookie 并利用它来搞破坏。
我们将探讨一些常见的攻击手段,并介绍对该攻击的应对策略。
另外,请注意,所有代码段都将使用 。如果要在服务器上实现这些修补程序,则需要查找语言或框架的确切语法。
中间人攻击
中间人(MitM)攻击描述了广泛的攻击类别,其中,攻击者在客户端和服务器之间,并拦截在两者之间传递的数据。
这可以通过多种方式完成:通过访问或收听不安全的网站,模仿公共 WiFi 路由器,DNS 欺骗或通过 SuperFish 一类的恶意软件/广告软件。
这是 MitM 攻击的比较深入的介绍概述,涉及了网站如何保护自己和用户。
作为开发人员,通过确保在服务器上启用 HTTPS,使用来自受信任的证书颁发机构的 SSL 证书以及确保代码使用 HTTPS 而不是不安全的 HTTP,可以大大减少 MitM 攻击的可能性。
就 cookie 而言,你应该在 cookie 中添加 Secure 属性,以便它们只能通过安全的 HTTPS 连接发送:
document.cookie = 'dark_mode=false; Secure';
请记住,Secure 属性实际上并不会加密 cookie 中的任何数据 – 它只是确保 cookie 无法通过 HTTP 连接发送。
但是,攻击者仍然可能会拦截和操纵 cookie。为了防止这种情况的发生,还可以使用 HttpOnly 参数:
document.cookie = 'dark_mode=false; Secure; HttpOnly';
带有 HttpOnly 的 cookie 只能由服务器访问,而不能由浏览器的 Document.cookie API 访问。这非常适合诸如登录会话之类的场景,在该会话中,只有服务器真正需要知道你是否已登录到站点,而客户端不需要该信息。
XSS 攻击
XSS(跨站点脚本)攻击描述了恶意用户将意想不到的、潜在危险的代码注入网站时的一类攻击。
这些攻击非常棘手,因为它们可能会影响访问该网站的每个人。
例如,如果某个站点具有评论功能,并且某人能够将恶意代码包含在评论中,则访问该站点并阅读该评论的每个人都有可能受到影响。
就 cookie 而言,如果恶意行为者在站点上成功发起 XSS 攻击,则他们可以访问会话 cookie 并以另一个已登录用户的身份访问该站点。这样,他们就可以访问其他用户的设置,以该用户的身份购买商品并将其运送到另一个地址,等各种操作。
这是一个视频,概述了 XSS 的不同类型 - Reflected、Stored,基于 DOM 的和 Mutation:
作为开发人员,要确保服务器执行“相同来源策略”,并确保正确过滤了从用户那里收到的任何输入。
就像防止 MitM 攻击一样,你应该为你使用的任何 cookie 设置 Secure 和 HttpOnly 参数:
document.cookie = 'dark_mode=false; Secure; HttpOnly';
CSRF 攻击
CSRF(跨站点请求伪造)攻击是指攻击者诱骗他人执行意想不到的,潜在的恶意行为。
例如,如果你登录到站点并单击评论中的链接,如果该链接是 CSRF 攻击的一部分,则可能会导致你无意中更改登录详细信息,甚至删除帐户。
虽然 CSRF 攻击在某种程度上与 XSS 攻击有关,特别是反映了有人将恶意代码插入站点的 XSS 攻击,但每种攻击都基于不同类型的信任。
根据 Wikipedia 的说法,虽然 XSS “利用了用户对特定站点的信任,但是 CSRF 却利用了站点在用户浏览器中访问的信任。 ”
这是一段解释 CSRF 基础知识的视频,并提供了一些有用的示例:
至于 cookie,防止可能的 CSRF 攻击的一种方法是使用 SameSite 标志:
document.cookie = 'dark_mode=false; Secure; HttpOnly; SameSite=Strict';
您可以为 SameSite 设置一些值:
- Lax: cookie 不会发送嵌入内容(图像,iframe 等),而是在你单击链接或向该 cookie 所设置的来源发送请求时发送。例如,如果你在 testing.com 上,然后单击链接以转到 test.com/about,则浏览器将发送带有该请求的 test.com cookie。
- Strict:仅当你单击链接或从设置了 cookie 的来源发送请求时,才会发送 cookie。 例如,只会在你位于 test.com 及其附近时发送 test.com cookie,而不会来自 testing.com 之类的其他网站。
- None:Cookie 会随每个请求发送,无论上下文如何。 如果将 SameSite 设置为 None,则还必须添加 Secure 属性。如果可能,最好避免使用此值。
主流浏览器对 SameSite 的处理方式略有不同。例如,如果未在 cookie上设置 SameSite,则 Google Chrome 默认将其设置为 Lax 。
cookie 的替代品
你可能想知道,如果 cookie 有这么多潜在的安全漏洞,为什么我们仍在使用它们? 当然,必须有更好的选择。
这些天来,你可以使用 sessionStorage 或 localStorage 来存储最初使用 cookie 的信息。对于有状态会话,还可以使用基于令牌的身份验证以及诸如 JWT(JSON Web令牌)之类的东西。
虽然似乎你必须在基于 cookie 的身份验证或基于令牌的身份验证之间进行选择,但也可以同时使用两者。 例如,当某人通过浏览器登录时,你可能要使用基于 cookie 的身份验证,而当某人通过电话应用程序登录时,则要使用基于令牌的身份验证。
为了进一步解决问题,Auth0 等 authentication-as-aservice 提供程序允许你同时进行两种身份验证。
如果你想了解有关 Web 令牌和基于令牌的身份验证的更多信息,请查看我们的一些文章。
当你向开发人员提供 cookie 时
就是这样! 这就是你开始使用 cookie 所需的知识,以及在此过程中需要注意的事项。
你觉得这有用吗? 你是如何使用 cookie? 留言或给我发推特吧。
原文:Everything You Need to Know About Cookies for Web Development,作者:Kris Koishigawa