选择存储方式更多是在权衡利弊,而不是试图找到一个明确的最佳选择。让我们来看几个选项:
选项1 - Web存储 (localStorage
或 sessionStorage
)
优点
- 浏览器不会自动将Web存储中的任何内容包含到HTTP请求中,使其不容易受到CSRF攻击
- 只能由在创建数据的完全相同域中运行的Javascript访问
- 允许使用最语义化正确的方法在HTTP中传递令牌身份验证凭据(
Authorization
头和Bearer
模式)
- 很容易挑选应该包含身份验证的请求
缺点
- 不能被在创建数据的子域中运行的Javascript访问(由
example.com
写入的值无法被sub.example.com
读取)
- ⚠️易受XSS攻击
- 为了执行经过身份验证的请求,只能使用浏览器/库API允许你自定义请求(在
Authorization
头中传递令牌)
用法
您可以利用浏览器的localStorage
或sessionStorage
API在执行请求时存储和检索令牌。
localStorage.setItem('token', 'asY-x34SfYPk'); // write
console.log(localStorage.getItem('token')); // read
选项2 - 仅HTTP的Cookie
优点
- 它不容易受到XSS攻击
- 浏览器会自动包含满足Cookie规范(域,路径和生命周期)的任何请求中的标记
- Cookie可以在顶级域中创建并用于由子域执行的请求
缺点
- ⚠️ 容易受到CSRF攻击
- 您需要注意并始终考虑子域中的Cookie可能的使用方式
- 挑选应该包括Cookie的请求是可行的,但会有些混乱
- 您可能会遇到一些小差异的问题,因为浏览器处理Cookie的方式不同
- 如果不小心,则可能会实现一个容易受到XSS攻击的CSRF缓解策略
- 服务器端需要验证Cookie以进行身份验证,而不是更合适的
Authorization
头
用法
不需要在客户端执行任何操作,因为浏览器会自动处理所有事情。
选项3 - Javascript可以访问的Cookie 被服务器端忽略
优点
- 不容易受到CSRF攻击(因为服务器端忽略它)
- Cookie可以在顶级域中创建并用于由子域执行的请求
- 允许使用最语义正确的方法通过HTTP传递令牌身份验证凭据(使用带有
Bearer
方案的Authorization
头)
- 较易挑选应该包含身份验证的请求
缺点
- ⚠️ 容易受到XSS攻击
- 如果设置Cookie的路径不当,则浏览器会自动在请求中包含Cookie,从而增加不必要的开销
- 为了执行身份验证请求,只能使用允许您自定义请求的浏览器/库API(在
Authorization
头中传递令牌)
用法
您可以利用浏览器的document.cookie API存储和检索令牌以便执行请求。这个API没有Web存储那么细粒度(您获取所有Cookie),因此需要额外的工作来解析所需的信息。
document.cookie = "token=asY-x34SfYPk"; // write
console.log(document.cookie); // read
补充说明
这可能看起来是一个奇怪的选项,但它有一个好处——你可以将存储空间提供给顶级域名和所有子域名,这是Web存储无法提供的。然而,实现更加复杂。
结论 - 最终说明
对于大多数常见情况,我建议选择选项1,主要原因如下:
- 如果你创建了一个Web应用程序,你需要处理XSS;无论你在哪里存储令牌,都需要处理
- 如果你不使用基于cookie的身份验证,CSRF甚至不会出现在你的视野中,所以这是少了一件需要担心的事情
另请注意,基于cookie的选项也有很大差异,对于选项3,cookie纯粹用作存储机制,因此它几乎就像是客户端的实现细节。然而,选项2意味着更传统的处理身份验证的方式;如果您想进一步了解cookie与token的区别,您可能会发现本文有趣:Cookies vs Tokens: The Definitive Guide。
最后,虽然没有任何一个选项提到这一点,但使用HTTPS是强制性的,这意味着cookie应该被适当地创建以考虑到这一点。
Set-Cookie
头选项中SameSite
属性的引入,选项2在可用性方面对CSRF攻击变得更加无懈可击。特别是在将Lax
设置为默认值后。换句话说,如果不是现在,至少在不久的将来,选项2可能会更安全。而且,“如果你不小心,你可能会实施一个对XSS攻击易受攻击的CSRF缓解策略”这句话并不清楚你的意思。 - x-yuri