我是否必须将令牌存储在 cookie、localstorage 或 session 中?

IT技术 javascript jwt
2021-02-02 15:22:51

我正在使用 React SPA、Express、Express-session、Passport 和 JWT。我对存储令牌的一些不同客户端存储选项感到困惑:Cookies、Session 和 JWT/Passport。

令牌是否必须存储在 cookie 中,即使我可以将它们存储在 cookie 中req.sessionID

许多网站使用 cookie 来存储购物车令牌。到目前为止,我已经根据会话 ID 存储了购物车数据,而没有添加任何 cookie。

因此,当用户访问我的网站时,我会将其与他们的网站进行匹配 req.sessionID,然后检索数据库中的数据,例如购物车和用户会话。

我需要储存cookies吗?我可以通过访问它req.sessionID来获取所需的数据。

第二个

我已经使用passport-google-oauth20.进行了身份验证。成功登录后,数据将保存到会话中。并将其发送给客户端,我必须通过 URL 查询发送它?token='sdsaxas'

在这种情况下,我得到了很多不同的意见。有人将其保存到本地存储中,有人通过使用 JWT 将其转换为令牌将其保存到 cookie 中。

 jwt.sign(
        payload,
        keys.jwt.secretOrPrivateKey, 
        {
            expiresIn:keys.jwt.expiresIn // < i dont know what is this expired for cookies or localstorage ?
        }, (err, token) => {

            res.redirect(keys.origin.url + "?token=" + token);
        });

我确实可以通过使用 sessionID(没有 cookie 或 localstorage)来存储与会话相关的所有内容吗?

由于我使用的是 React SPA,因此只能通过 fetch 一次或每次页面刷新并检索数据然后保存到 redux 中。

4个回答

这个答案基于无状态方法,因此它不讨论传统的会话管理

你问了两个完全不同的问题:

  1. 购物车 - 与业务功能更相关
  2. OAuth 2 & JWT - 与安全和身份验证相关

作为电子商务网站的用户,我希望我在上班途中从移动设备添加到购物车的任何商品,在我回家后从 PC 登录网站时都应该在购物车中可用。因此,购物车数据应保存在后端数据库中并链接到我的用户帐户。

当涉及到使用 OAuth 2.0 进行身份验证时,JWT 访问令牌和/或刷新令牌需要存储在客户端设备的某个位置,这样一旦用户通过提供登录凭据对自己进行身份验证,他就不需要再次提供他的凭据浏览网站。在这种情况下,浏览器本地存储、会话存储和 cookie 都是有效的选项。但是,请注意,此处的 cookie 未链接到服务器端的任何会话。换句话说,cookie 不存储任何会话 ID。cookie 仅用作访问令牌的存储,访问令牌随每个 http 请求传递到服务器,然后服务器使用数字签名验证令牌以确保它没有被篡改和过期。

尽管访问和/或刷新令牌的所有三个存储选项都很流行,但如果以正确的方式使用,cookie 似乎是最安全的选项。

为了更好地理解这一点,我建议您阅读本文本文以及 OAuth 2.0 规范。

2019 年 2 月 16 日更新

我之前说过 cookie 似乎是最安全的选项。我想在这里进一步澄清这一点。

我认为浏览器localStoragesessionStorage没有为存储身份验证令牌提供足够的安全性的原因如下:

  1. 如果发生 XSS,恶意脚本可以轻松地从那里读取令牌并将它们发送到远程服务器。在那里,远程服务器或攻击者在冒充受害用户方面没有问题。

  2. localStorage并且sessionStorage不跨子域共享。因此,如果我们有两个 SPA 在不同的子域上运行,我们将无法获得 SSO 功能,因为组织内的另一个应用程序将无法使用一个应用程序存储的令牌。有一些使用 的解决方案iframe,但这些看起来更像是解决方法而不是好的解决方案。并且当响应头X-Frame-Options被用来避免点击劫持攻击时iframe,任何解决方案iframe都是不可能的。

但是,可以通过使用指纹(如OWASP JWT 备忘单中所述来减轻这些风险,而指纹又需要 cookie。

指纹的想法是,生成一个加密的强随机字节串。原始字符串的 Base64 字符串将存储在名称为 prefixHttpOnly, Secure, SameSitecookie 中__Secure-应根据业务需求使用域和路径属性的正确值。字符串的 SHA256 哈希值也将在 JWT 的声明中传递。因此,即使 XSS 攻击将 JWT 访问令牌发送到攻击者控制的远程服务器,它也无法发送 cookie 中的原始字符串,因此服务器可以基于 cookie 的缺失拒绝请求。HttpOnlyXSS 脚本无法读取cookie

因此,即使我们使用localStorageand sessionStorage,我们也必须使用 cookie 来确保它的安全。最重要的是,我们添加了如上所述的子域限制。

现在,使用 cookie 存储 JWT 的唯一问题是 CSRF 攻击。由于我们使用SameSitecookie,CSRF 得到缓解,因为跨站点请求(AJAX 或仅通过超链接)是不可能的。如果该网站在任何旧浏览器或其他一些不支持的不太流行的浏览器中使用SameSitecookie,我们仍然可以通过额外使用具有加密强随机值的 CSRF cookie 来缓解 CSRF,这样每个 AJAX 请求都会读取 cookie 值并将 cookie 值添加到自定义 HTTP 标头中(除了不应该这样做的 GET 和 HEAD 请求)任何状态修改)。由于CSRF由于同源策略无法读取任何内容,并且它基于利用POST,PUT和DELETE等不安全的HTTP方法,因此该CSRF cookie将减轻CSRF风险。所有现代 SPA 框架都使用这种使用 CSRF cookie 的方法。这里提到Angular 方法

此外,由于 cookie 是httpOnlyand Secured,XSS 脚本无法读取它。因此,XSS 也得到了缓解。

还值得一提的是,可以通过使用适当的content-security-policy响应头进一步减轻 XSS 和脚本注入

其他CSRF缓解方法

  1. 状态变量(Auth0 使用它) - 客户端将生成并随每个请求传递一个加密的强随机随机数,服务器将与其响应一起回显,允许客户端验证随机数。它在Auth0 doc 中进行了解释
  2. 始终检查引用标头并仅在引用为受信任域时才接受请求。如果引用标头不存在或域未列入白名单,只需拒绝请求即可。使用 SSL/TLS 时,通常会出现引用。登陆页面(主要是信息性的,不包含登录表单或任何安全内容)可能有点放松,并允许缺少引用标头的请求。
  3. 应在服务器中阻止 TRACE HTTP 方法,因为这可用于读取httpOnlycookie。
  4. 另外,设置标头 Strict-Transport-Security: max-age=; includeSubDomains 只允许安全连接,以防止任何中间人覆盖来自子域的 CSRF cookie。
@Kaiido 感谢您的反馈。但是使用这种方法,XSS 只能从用户的浏览器中做它打算做的任何伤害。远程模拟的可能性要小得多。
2021-03-16 15:22:51
@BasilMusa 这里发生了两件事 - 1. cookie 不会随 HTTP 请求一起传送到攻击者的服务器,因为 cookie 域属性将与攻击者的服务器域不匹配,并且因为 cookie 是SameSitecookie。2. 现在,由于 cookie 也是httpOnly,XSS javascript 将无法从 cookie 中读取它并在 AJAX 请求中发送指纹值。
2021-03-17 15:22:51
@BasilMusa 感谢您撰写反馈。我认为我的意思与你所说的没有什么不同,但想知道哪个陈述让你认为我误解了HttpOnly
2021-03-22 15:22:51
我认为您在 cookie 中误解了 HttpOnly。HttpOnly 意味着 javascript 无法读取“document.cookie”属性。但是,javascript http 请求仍然将请求标头中的 cookie 值发送到服务器。
2021-03-23 15:22:51
并不是说我不同意一般的答案,但是如果有人成功地进行了 XSS 渗透,那么他们就不需要令牌。
2021-04-01 15:22:51

LocalStorage/SessionStorage 容易受到 XXS 攻击。访问令牌可以被 JavaScript 读取。

带有 httpOnly、secure 和 SameSite=strict 标志的 Cookie 更安全。JavaScript 无法访问访问令牌及其有效负载。

但是,如果存在 XSS 漏洞,攻击者无论如何都可以以经过身份验证的用户身份发送请求,因为恶意脚本不需要读取 cookie 值,cookie 可以由浏览器自动发送。

这种说法是正确的,但风险不同。

使用cookies,访问令牌仍然是隐藏的,攻击者只能进行“现场”攻击。注入到 Web 应用程序中的恶意脚本可能会受到限制,或者更改/注入更多脚本可能不是很容易。攻击者可能需要首先将用户或 Web 应用作为目标。这些条件限制了攻击的规模。

使用 localStorage,攻击者可以读取访问令牌并远程进行攻击。他们甚至可以与其他攻击者共享令牌并造成更严重的损害。如果攻击者设法在 CDN 中注入恶意脚本,比如谷歌字体 API,攻击者将能够从使用包含的 CDN 的所有网站中提取访问令牌和 URL,并轻松找到新的目标。使用 localStorage 的网站更容易成为目标。

为了辩论

渗透测试可能会将您对敏感数据使用 localStorage 标记为风险。

如果 JavaScript 可以从 XSS 攻击中从 localStorage 读取访问令牌,那么您认为 httpOnly 标志为什么仍然被大家推荐。

来自 OWASP 的推荐

不要将会话标识符存储在本地存储中,因为 JavaScript 始终可以访问数据。Cookie 可以使用 httpOnly 标志降低这种风险。

https://medium.com/@coolgk/localstorage-vs-cookie-for-jwt-access-token-war-in-short-943fb23239ca

HTTP 是一种无状态协议。阅读该答案以获取更多详细信息,但本质上这意味着 HTTP 服务器(例如您的 Web 服务器)不会在一个请求的生命周期之后存储有关客户端的任何信息。这是 Web 应用程序的一个问题,因为这意味着您无法记住哪个用户已登录。

饼干的发明就是为了解决这个问题。Cookie 是客户端和服务器在每次请求时来回发送的文本数据它们允许您有效地维护应用程序状态数据,让客户端和服务器就他们每次通信时记住的内容达成一致。

这意味着,从根本上说,没有 cookie 就无法进行会话必须是一个cookie,存储至少会话ID,这样就可以找出哪些用户通过查找会话当前登录到您的应用程序。这就是 express-session 所做的:主要方法的文档session明确指出会话 ID 存储在 cookie 中。

所以我的问题是我需要存储 cookie 吗?因为我可以通过 req.sessionID 访问它来获取所需的数据。

不需要存储 cookie。express-session 会为你做这件事。您的应用程序作为一个整体确实需要存储一个 cookie;没有它,你就不必req.sessionID抬头。

@SaptarshiBasu express-session 的文档明确表示它确实使用服务器端会话,只是将会话ID 存储在 cookie 中。
2021-03-22 15:22:51
此类应用程序中不管理服务器端会话。相反,cookie 用于存储身份验证令牌而不是会话 ID。服务器端会话难以扩展,我们正在远离基于微服务的架构。
2021-04-03 15:22:51

根据我的经验,只需将令牌存储在 localStorage 中。

本地存储

它可以存储高达 5MB 的信息。在 localStorage 中存储令牌不需要征得用户的许可。

唯一需要担心的是目标设备是否支持localStorage api。

在这里查看:https : //developer.mozilla.org/en-US/docs/Web/API/Window/localStorage

它得到了广泛的支持。但是根据我的经验,如果你有一个ios应用程序,并且这个应用程序中有一个要求用户存储令牌的html页面(也称为webview),则无法识别localStorage api并引发错误。

解决方案很简单,我只是将令牌放入 url 并每次都传输它。在 webview 中,url 不可见。

曲奇饼:

在本地存储信息是一种非常古老的风格。cookie 中的存储量相对较小,您需要征得用户的许可才能将令牌存储在 cookie 中。

每个请求都会发送 Cookie,因此它们会降低性能(尤其是对于移动数据连接)。用于客户端存储的现代 API 是 Web 存储 API(localStorage 和 sessionStorage)和 IndexedDB。( https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies )

不要将令牌存储在 sessionStorage 或 redux 中。

如果选项卡关闭,存储在 sessionStorage 中的数据将丢失。如果用户不小心关闭了选项卡,令牌将丢失,服务器将无法识别当前用户。存储在 redux 中的 token 与存储在其他 js 文件中没有区别。redux store 只是另一个 js 文件。每次页面刷新时,存储在 redux 中的信息都会丢失。

综上所述,

大多数情况下,如果使用现代风格,令牌存储在 localStorage 中。在某些情况下,您可以将令牌存储在 cookie 中,有时可能会放在 url 中。但永远不要存储在会话中。

希望能帮助到你。

您不能跨子域使用 localstorage,这是一个常见要求。Localstorage 容易受到 XSS 攻击。当您将令牌存储在 URL 中时,安全性就会消失。Cookie 并不是一种古老的方式,使用 cookie 是非常普遍的,并且在以正确的方式使用时它可以提供适当的安全级别。这是关于存储身份验证令牌而不是 5MB 图像,因此存储空间并不重要
2021-03-19 15:22:51
CSRF 使用额外的 CSRF cookie 和身份验证令牌 cookie 来保护。Localstorage 是用于客户端存储的现代 api,只是它没有为身份验证令牌提供足够的安全性。仍然有一些应用程序确实使用 localstorage 作为身份验证令牌,但不能如此明确地推荐它。关于 iframe hack,您真的喜欢这种解决方法吗?
2021-03-21 15:22:51
使用 cookie 会使您暴露在 CSRF 攻击中,而 localStorage 则不会。stackoverflow.com/questions/35291573/…还要检查链接developer.mozilla.org/en-US/docs/Web/HTTP/Cookies,它说 localStorage 是现代 API。如果用户不授予您使用 cookie 的权限怎么办?关于子域:有解决方法。在此处查看:stackoverflow.com/questions/4026479/... SPA,将子域设为组件,有一个 html。不同的url只是不同的js文件。
2021-04-03 15:22:51