【问题标题】:Do I have to store tokens in cookies or localstorage or session?我必须将令牌存储在 cookie 或本地存储或会话中吗?
【发布时间】:2019-06-12 23:16:36
【问题描述】:

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

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

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

所以当用户访问我的网站时,我会将其与他们的 req.sessionID 然后检索数据库中的数据,如购物车和用户会话。

我需要存储 cookie 吗?我可以通过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 或本地存储)存储与会话相关的所有内容吗?

因为我使用 React SPA,所以只需要进行一次或每次页面刷新并检索数据然后保存到 redux 中。

【问题讨论】:

  • cookies 和 localstorage 用于在客户端浏览器中保持会话。您可以使用它们重新验证返回的用户。我不熟悉 sessionID 键,但我必须假设它是由护照中间件根据您的 queryString ?token= 设置到 express req 对象中的。您可能将其作为全局变量存储在您的应用程序中,如果用户关闭浏览器,它将丢失。如果您想稍后存储此令牌值,请使用 cookie 或 localstorage。或者你可以让用户再次登录,完全省去麻烦!
  • 我只是在寻找参考资料和许多让我感到困惑的差异@Quentin 也许你能解释一下?
  • 我只是对在购物车上使用 cookie 和用户身份验证以确保安全 感到困惑
  • 除非您有特殊需要,否则只需使用localStorage 存储JWTs。大多数用于身份验证的 js 库或框架插件已经这样做了。

标签: javascript jwt


【解决方案1】:

这个答案是基于无状态的方法,因此它没有谈论传统的会话管理

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

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

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

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

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

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

2019 年 2 月 16 日更新

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

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

  1. 如果发生 XSS,恶意脚本可以轻松地从那里读取令牌并将其发送到远程服务器。从那里开始,远程服务器或攻击者在冒充受害者用户时不会有任何问题。

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

不过,这些风险可以通过使用指纹(如 OWASP JWT Cheat Sheet 中所述)来缓解,而指纹又需要 cookie。

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

因此,即使我们使用localStoragesessionStorage,我们也必须使用cookie 来确保其安全。最重要的是,我们添加了上面提到的子域限制。

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

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

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

其他 CSRF 缓解方法

  1. 状态变量(Auth0 使用它)- 客户端将生成并随每个请求传递一个加密的强随机随机数,服务器将连同其响应一起回显该随机数,从而允许客户端验证随机数。 Auth0 doc 中对此进行了解释。
  2. 始终检查referer 标头并仅在referer 是受信任域时接受请求。如果没有引用标头或未列入白名单的域,则只需拒绝请求。使用 SSL/TLS 时,通常会出现引用。登陆页面(主要是信息性的,不包含登录表单或任何安全内容)可能有点放松,并允许缺少引用标头的请求。
  3. 应在服务器中阻止 TRACE HTTP 方法,因为这可用于读取 httpOnly cookie。
  4. 另外,设置标题 Strict-Transport-Security: max-age=; includeSubDomains​只允许安全连接,以防止任何中间人覆盖子域中的 CSRF cookie。

【讨论】:

  • 并不是我不同意一般的答案,但是如果一个人成功地进行了 XSS 渗透,那么他们就不需要令牌。
  • @Kaiido 感谢您的反馈。但是通过这种方法,XSS 只能从用户的浏览器做任何它打算做的伤害。远程模仿的可能性要小得多。
  • 我想你误解了cookies中的HttpOnly。 HttpOnly 表示 javascript 无法读取“document.cookie”属性。但是,javascript http 请求仍然会将请求标头中的 cookie 值发送到服务器。
  • @BasilMusa 感谢您撰写反馈。我认为我的意思与您所说的没有什么不同,但想知道哪个陈述让您认为我误解了HttpOnly
  • @BasilMusa 这里发生了两件事 - 1. cookie 不会随 HTTP 请求一起传送到攻击者的服务器,因为 cookie 域属性与攻击者的服务器域不匹配,并且因为 cookie 是SameSite cookie。 2. 现在,由于cookie也是httpOnly,XSS javascript将无法从cookie中读取它并在AJAX请求中发送指纹值。
【解决方案2】:

LocalStorage/SessionStorage 容易受到 XXS 攻击。 Access Token 可以被 JavaScript 读取。

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

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

此说法属实,但风险不同。

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

使用 localStorage,攻击者可以读取访问令牌并进行远程攻击。他们甚至可以与其他攻击者共享令牌并造成更严重的破坏。如果攻击者设法在 CDN 中注入恶意脚本,比方说 google fonts 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

【讨论】:

    【解决方案3】:

    HTTP is a stateless protocol. 阅读该答案以获取更多详细信息,但本质上这意味着 HTTP 服务器(例如您的 Web 服务器)在一个请求的生命周期之外不会存储有关客户端的任何信息。这对于网络应用来说是个问题,因为这意味着您无法记住登录的是哪个用户。

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

    这意味着,从根本上说,您不能在没有 cookie 的情况下进行会话必须有一个 cookie 来存储至少会话 ID,以便您可以通过查找会话找出当前登录到您的应用程序的用户。这就是 express-session 的作用:the documentation 用于主要的 session 方法,明确指出会话 ID 存储在 cookie 中。

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

    不需要存储 cookie。 express-session 将为您执行此操作。您的应用程序作为一个整体是否需要存储一个 cookie;没有它,您将无法查找 req.sessionID

    【讨论】:

    • 在这种类型的应用程序中不管理服务器端会话。相反,cookie 用于存储身份验证令牌,而不是会话 ID。服务器端会话难以扩展,我们正在远离基于微服务的架构。
    • @SaptarshiBasu express-session 的文档明确表示它确实使用服务器端会话,只是将会话 ID 存储在 cookie 中。
    【解决方案4】:

    根据我的经验,只需将令牌存储在 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中的存储量比较小,需要先征得用户的同意才能将token存储在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 中的信息。

    总之,

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

    希望对你有帮助。

    【讨论】:

    • 您不能跨子域使用本地存储,这是一个常见的要求。 Localstorage 容易受到 XSS 攻击。当您将令牌存储在 URL 中时,安全性就会消失。 Cookie 并不是一种古老的方式,使用 cookie 非常普遍,并且当以正确的方式使用时,它可以提供适当的安全级别。这是关于存储身份验证令牌而不是 5MB 图像,因此存储空间无关紧要
    • 使用 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文件。
    • CSRF 使用附加的 CSRF cookie 和身份验证令牌 cookie 来保护。 Localstorage 是用于客户端存储的现代 api,只是它没有为身份验证令牌提供足够的安全性。仍然有应用程序使用本地存储作为身份验证令牌,但不能如此明确地推荐。关于 iframe hack,您真的喜欢这种解决方法吗?
    猜你喜欢
    • 1970-01-01
    • 2021-10-08
    • 2017-02-11
    • 2019-10-26
    • 2020-12-26
    • 1970-01-01
    • 2018-08-02
    • 1970-01-01
    • 2017-07-02
    相关资源
    最近更新 更多