【问题标题】:How to securely implement authentication in Single Page Applications (SPAs) with a decoupled API如何使用解耦 API 在单页应用程序 (SPA) 中安全地实现身份验证
【发布时间】:2022-06-10 17:42:26
【问题描述】:

我一直在研究如何最好地在 React/Next.js 应用程序中实现身份验证 - 具体来说,如何以实用的方式存储身份验证令牌,同时保持对安全性的必要重视。关于 SO 的这个话题存在一些现有的争论,但据我所知,没有一个提供具体的解决方案。

昨天和今天花了很多时间在互联网上寻找答案,我遇到了以下问题:

  • Local Storage API。我发现一些基本指南建议使用localStorage尽管许多人理所当然地反对它)。我不喜欢这种方法,因为在发生 XSS 攻击时可以访问存储在 localStorage 中的数据。

  • Web Worker。如果令牌存储在 Web Worker 中,则在打开新选项卡时用户将不会登录。这会导致不合标准且令人困惑的用户体验。

  • 关闭。与 Web Workers 相同 - 没有持久性。

  • HttpOnly Cookies。一方面,此类 cookie 无法被 JavaScript 读取,因此不易发生 XSS。然而,另一方面,我们现在必须处理 CSRF,这完全是一个新的争论:如何使用 SPA + REST API 实现 CSRF 令牌?

虽然使用HttpOnly cookie 似乎是最有利的,但在 SPA 中实施 CSRF 似乎是非常规的,并且可能是实验性的,这违反了在安全性方面不“自己动手”的格言。如果存在,我宁愿使用一种经过验证的方法。

如今 SPA 风靡一时,令我感到惊讶的是,要为如此普遍的场景找到共识(甚至更好的是库)变得如此困难。

其他人是怎么做的?


更新:经过一番思考,我想知道如果有强大的 CORS 政策,localStorage 是否真的那么糟糕?如果碰巧存在 XSS 漏洞,攻击者难道不能开始从浏览上下文中发送请求,从而否定使用 cookie 和 CSRF 保护的任何感知好处吗?

【问题讨论】:

    标签: rest security single-page-application csrf xss


    【解决方案1】:

    您的比较有点令人困惑...当您谈论 SPA 身份验证时,您需要一个允许您存储身份验证令牌的解决方案。 Web Workers 和 Closures 的主要目的是运行代码,而不是存储身份验证令牌。您可以将其写为硬编码变量,但这不是它的目的,这就是新标签无法识别它的原因。

    所以我们只剩下本地存储和会话 Cookie。 在 SPA 和客户端渲染之前,我们曾经只有服务器端渲染和 cookie。这就是 HTTPOnly 的发明,它使窃取会话 ID 和用户身份变得更加困难。当发明客户端渲染时,发明了无状态 API。这些 API 不使用会话 ID,而是使用令牌,例如 JWT。这意味着服务器不会在由会话 ID 标识的会话中保存有关用户的任何信息。相反,JWT 令牌包含令牌本身的签名信息,服务器不记得任何内容并在每个请求中重新验证用户。还有一种混合令牌方法,将令牌保存在服务器端的 NoSQL DB 中,例如 Redis,以加快身份验证过程。

    但底线是 HTTPOnly cookie 与使用会话的有状态 API 一起使用,而 LocalStorage 与使用令牌的无状态 API 一起使用。您也可以反过来使用它(带有令牌的 cookie、带有会话的 LocalStorage),但它“不太自然”。

    对于 XSS,cookie 中的 HttpOnly 机制让攻击者的生活更加艰难,但即使使用它,攻击者仍然可以通过网络钓鱼等方式造成很大的破坏。所以它并不重要,因为这只是一个补偿控制,而不是任何主要的缓解措施,所以你可以安全地使用 LocalStorage。此外,cookie 容易受到 CSRF 攻击,而 LocalStorage 则不会。 所以这两个选项都有效。

    您可以在这里阅读更多内容: What is the difference between localStorage, sessionStorage, session and cookies?

    【讨论】:

    • 如果您需要在多个选项卡上进行相同的身份验证,您仍然可以使用将令牌存储在窗口范围内的任何选项,但单独管理令牌并在页面刷新时获取新令牌(静默刷新,提示=无流)。这打开了一些选项:服务工作者、网络工作者、闭包(是的,其中一些最初并不是为了那个,但可以很好地解决问题)。这也解决了一堆关于刷新令牌的竞争条件(它们只能被使用,所以每个标签都有一个解决了一堆问题)。
    【解决方案2】:

    很高兴您提出这个问题。前端经常出现关于 oauth2 的模因,这些模因确实污染了辩论,而且很难找到事实信息。

    首先,关于我建议重新考虑的一些排除选项:如果您需要在多个选项卡上进行相同的身份验证,您仍然可以使用将令牌存储在窗口范围内的任何选项,但单独管理令牌并在页面刷新时获得一个新的(静默刷新,因此标准提示=无流)。这会打开一些选项:服务工作者、网络工作者、闭包……的确,其中一些原本不是为了那个,但它很好地解决了问题。这也解决了一堆关于刷新令牌的竞争条件(它们只能使用一次,因此每个选项卡都有一个可以解决一堆问题)。

    话虽如此,以下是选项:

    • 本地存储:如果 XSS 攻击成功,令牌可能会被盗。 XSS=game over 不管怎样,IMO(在这种情况下,没有黑客会关心你的令牌,这不是必需的)。与 cookie 的典型小时/天有效期相比,它也可以通过使用短期令牌来缓解。无论如何,建议使用短期令牌。

      现在,XSS 中的令牌被盗似乎对某些人来说是一个重要问题,所以让我们看看其他选项。

    • 会话存储:与本地存储相同的缺点(XSS 可能导致令牌泄漏),但也解决了一些问题(刷新...)。

    • 网络工作者:这实际上是一个不错的解决方案。如果在应用程序的随机部分成功 XSS,它将无法窃取令牌。从理论上讲,如果可以注入一些将在身份验证时运行的脚本(身份验证代码或令牌交换),它也可能被利用……但这适用于所有流,包括 cookie/会话。

    • 闭包:与网络工作者相同。不那么孤立(更容易被会窃取您的令牌的替代)。

    • Service Worker:在我看来是理想的解决方案。易于实现(您只需拦截获取请求并在几行代码中添加您的令牌)。不能被 XSS 注入。您甚至可以争辩说它实际上是针对那个确切的用例的。它还解决了一个页面上有多个应用程序的情况(它们共享 1 个在需要时添加令牌的服务工作者),其他选项都不能很好地解决这个问题。唯一的缺点:浏览器可以终止它,你需要实现一些东西来延长它的生命周期(但是有一个文档化的标准方法)。

    • HttpOnly Cookies:简而言之,您正在过渡到传统的服务器端 Web 应用程序(至少在某些部分),它不再是具有标准 oidc 或 aouth2 的独立 SPA。这是一个选择(几年前不是我的选择),但它不应该受到令牌存储的驱动,因为有一些选项甚至更安全并且可以说更好。

    结论:我的建议是只使用本地/会话存储。无论如何,成功的 XSS 可能会让您失去工作或客户(提示:当他们可以调用 pay(5000000, lulzsecAccount) API 时,没有人对您的令牌感兴趣)。

    如果您对令牌存储挑剔,Service Worker 是 IMO 的最佳选择。

    【讨论】:

      猜你喜欢
      • 2018-03-01
      • 2014-05-14
      • 2013-01-25
      • 2016-12-27
      • 2018-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多