我忘记了我的旧答案仍然在互联网上引起了一些关注......而且我后来意识到它有一部分是错误的。
更新答案:
I thought about storing the token in a cookie, but that doesn't seem very safe.
如果操作正确,就足够安全了。是的,任何对系统有物理访问权限的人仍然可以读取 cookie。更多内容请参见下文。
让我们明确一点,绝对有必要在客户端(本例中为浏览器)存储一些数据,并通过 API 调用发送这些数据以对用户进行身份验证。我们称这个数据为“令牌”。
当您在 API 调用中发送此令牌时,任何对系统具有物理访问权限的人都可以查看它。此外,没有必要对其进行加密,因为...
- 您需要在发送前对其进行解密,这样才能使未加密的字符串可读。
- 您还需要将解密密钥存储在客户端,这让我们又回到了同样的问题。
有哪些选择?老实说,顺其自然吧。大多数网站(几乎)都是这样工作的。除非该网站包含一些非常敏感的信息。在这种情况下,是否可以查看基于时间的密码、基于硬件的密码、生物特征?这些方法大多将安全保管密钥的责任转嫁给用户。
您当然可以让它更安全。以下是一些提示:
- 在一段时间/不活动后从服务器中删除令牌。
- 根据随机请求更新令牌,并使之前的请求无效。
- 允许用户查看和删除活动会话。
- 将其绑定到用户 IP 或难以复制的东西。每次用户使用不同的 IP 登录时,都要求输入密码。
考虑到这一点,让我们谈谈存储令牌。
我们只需要确保其他网站、恶意脚本和软件无法访问存储的令牌。 (如上所述,用户始终可以读取令牌。)
您可以将其存储在cookies 或localStorage。两者都可以正常工作,但localStore 旨在存储更大的数据。 Cookies 最多可以存储 4096 字节的数据 - 这足以存储令牌。 Cookies 在处理 SSR(服务器端渲染)时也有帮助。不过,在 React 中处理 cookie 可能会很棘手。 (提示:试试 next.js,它内置了对 cookie 和 React 的 SSR 的支持。)
如果有帮助,您还可以在 cookie 中指定过期时间。
TL;DR: 使用Cookies 非常好。正确使用即可。
感谢@ShayanSalehian 指出这一点:LocalStorage is subject to XSS and cookie is subject to CSRF. So I think using cookies + CSRF is the most secure way even in TokenAuthentication for storing tokens on client...
旧答案(剧透警告,部分错误。):
这也是我必须处理的事情。在此过程中失眠了几个晚上。
免责声明:我不是任何安全专家。只是有点痴迷(阅读:偏执)。
短版(回答您的问题):我最终使用 window.localStorage 来存储令牌。虽然我不相信自己这是最好的做法,但这不仅仅是关于“存储”部分 - 阅读长版本以了解更多信息。
加长版:
首先,让我们澄清一些事情。 React 更像是一个移动应用程序,而不是网页/网站。我不是在谈论 React Native - 我的意思是 React.js。
为什么我说它更像是一个移动应用而不是一个网站?
传统网站通常使用基于会话的身份验证,通常会为此准备好浏览器/服务器。显然,这是一项简单而无缝的任务。
在移动应用程序(或客户端独立应用程序)中,您需要维护某种令牌来基本上告诉服务器“嘿,是我!我前段时间访问过。这是我的身份证。让我进去,请?”。问题是,在客户端很难保证令牌的安全。 Android 本身直到 Android v4.3 才提供任何安全的方式来存储身份验证令牌。这也不够安全,所以他们不久前引入了硬件支持的密钥库。这就是为什么某些应用程序不能(并且仍然不能)与有根设备一起使用的原因。在此处阅读更多信息:https://stackoverflow.com/a/19669719/3341737
与 React/独立网络应用程序相比,Google(在某种程度上)控制着 Android 客户端。他们实现基于硬件的密钥库相对容易。对于网络应用程序,有大量的浏览器,有数百个版本等等。
回到 window.localStorage。与 Cookie 类似,localStorage 对每个域都是隔离的。由于它是一个较新的 API,因此它的设计方式比旧的 Cookie 更好。
加密密钥没有意义(尽管您可能会混淆它),因为您还需要将解密密钥存储在本地某处。因此,如果有人可以访问令牌,他们也可以访问解密密钥。
这个问题的第二个方面(以及为什么“存储”不是唯一的问题)是 - 你真的想保护谁的令牌?
- 中间的人?使用 SSL。
- 其他网站?他们无法访问您域的 localStorage。
- 某个人?真的。如果他们对 PC 有物理访问权限,他们就可以轻松获得令牌。拥有物理访问权限实际上使他们成为用户(您想保护令牌不被用户访问?)。考虑到此人具有物理访问权限,即使您以某种方式安全地存储令牌,您也无法保护令牌。
为什么不呢?因为您需要随每个请求发送令牌 - 随每个请求发送的数据在浏览器网络检查器中可用。因此,无论您将令牌存储在何处以及如何存储,它都可能被具有 PC 物理访问权限的人窃取。
为什么不使用 Cookie?两个原因(实际上是 1 个):
- 默认情况下,每个请求都会发送 Cookie。你真的不需要这个,因为你只需要在 API 调用期间发送令牌(而不是页面加载)。此外,由于您使用的是 DRF(适用于任何 RESTful API 后端),您可能需要将令牌发送到服务器的特定方式 - 任何方式都需要自定义方法。
- window.localStorage 是一个简洁的小 API(只需
window.localStorage.setItem('key', 'value'))。此外,与 Cookie 相比,最大大小限制要高得多。
因此,window.localStorage 对我来说似乎是一个可行的选择。如果您有更好的解决方案,请启发我。
话虽如此,但这并不意味着您无法提高安全性。以下是一些建议:
- 在一段时间或一段时间不活动后,使令牌从数据库中失效/删除。
- 随请求随机发送更新的令牌(也使之前的令牌无效),并用新的令牌替换存储的令牌。
- 授予用户删除其帐户中保存的令牌以及每个令牌的最后活动的权限。
- 如果您真的(真的)担心,请将令牌与用户 IP(或其他无法克隆到另一个系统的东西)绑定。如果用户从新设备/位置/浏览器登录,请使用 2FA。