选择存储更多的是权衡,而不是试图找到明确的最佳选择。让我们来看看几个选项:
选项 1 - 网络存储(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 的可能用途
- Cherry 选择应该包含 cookie 的请求是可行的,但更麻烦
- 您可能(仍然)遇到一些问题,浏览器处理 cookie 的方式略有不同
- ⚠️ 如果你不小心你可能会实施一个容易受到 XSS 攻击的 CSRF 缓解策略
- 服务器端需要验证 cookie 进行身份验证,而不是更合适的
Authorization
header
用法
您不需要在客户端做任何事情,因为浏览器会自动为您处理事情。
选项 3 -服务器端忽略Javascript 可访问 cookie
优点
- 它不容易受到 CSRF 的影响(因为它被服务器忽略了)
- cookie 可以在顶级域中创建并用于子域执行的请求中
- 允许使用语义最正确的方法在 HTTP(
Authorization
带有Bearer
方案的标头)中传递令牌身份验证凭据
- 挑选应该包含身份验证的请求有点容易
缺点
- ⚠️ 易受 XSS 攻击
- 如果您不小心设置 cookie 的路径,那么浏览器会自动将 cookie 包含在请求中,这将增加不必要的开销
- 为了执行经过身份验证的请求,您只能使用允许您自定义请求的浏览器/库 API(在
Authorization
标头中传递令牌)
用法
document.cookie
在执行请求时,您可以利用浏览器API 来存储和检索令牌。此 API 不像 Web 存储(您获得所有 cookie)那样细粒度,因此您需要额外的工作来解析您需要的信息。
document.cookie = "token=asY-x34SfYPk"; // write
console.log(document.cookie); // read
补充笔记
这似乎是一个奇怪的选择,但它确实有一个很好的好处,即您可以将存储用于顶级域和所有子域,这是 Web 存储不会给您的。然而,它的实现更加复杂。
结论 - 最后的笔记
我对最常见场景的建议是使用 Option 1,主要是因为:
- 如果您创建一个 Web 应用程序,您需要处理 XSS;始终,独立于您存储令牌的位置
- 如果您不使用基于 cookie 的身份验证 CSRF 甚至不应该出现在您的雷达上,因此无需担心
另请注意,基于 cookie 的选项也有很大不同,因为选项 3 cookie 纯粹用作存储机制,因此它几乎就像是客户端的实现细节。然而,选项 2 意味着更传统的处理身份验证的方式;要进一步阅读 cookie 与令牌的相关内容,您可能会发现这篇文章很有趣:Cookie 与令牌:权威指南。
最后,没有一个选项提到它,但使用 HTTPS 当然是强制性的,这意味着应该适当地创建 cookie 以考虑到这一点。