【问题标题】:SPA best practices for authentication and session management认证和会话管理的 SPA 最佳实践
【发布时间】:2014-01-24 15:25:00
【问题描述】:

在使用 Angular、Ember、React 等框架构建 SPA 风格的应用程序时,人们认为哪些是身份验证和会话管理的最佳实践?我可以想出几种方法来考虑解决这个问题。

  1. 假设 API 和 UI 具有相同的源域,与使用常规 Web 应用程序进行身份验证没有区别。

    这可能涉及拥有会话 cookie、服务器端会话存储和可能的会话 API 端点,经过身份验证的 Web UI 可以访问这些端点以获取当前用户信息,以帮助进行个性化,甚至可能确定客户端的角色/能力。当然,服务器仍然会强制执行保护数据访问的规则,UI 只会使用这些信息来定制体验。

  2. 像使用公共 API 的任何第三方客户端一样对待它,并使用某种类似于 OAuth 的令牌系统进行身份验证。客户端 UI 将使用此令牌机制来验证对服务器 API 发出的每个请求。

我在这方面并不是真正的专家,但对于绝大多数情况来说,#1 似乎完全足够了,但我真的很想听听一些更有经验的意见。

【问题讨论】:

标签: security angularjs authentication ember.js single-page-application


【解决方案1】:

这个问题已经以稍微不同的形式详细地解决了,在这里:

RESTful Authentication

但这从服务器端解决了它。让我们从客户端来看这个。不过,在我们这样做之前,有一个重要的前奏:

Javascript Crypto 是绝望的

Matasano 关于这方面的文章很有名,但其中包含的教训非常重要:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

总结一下:

  • 中间人攻击可以用<script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script> 轻松替换您的加密代码
  • 对于通过非 SSL 连接提供任何资源的页面,中间人攻击是微不足道的。
  • 拥有 SSL 后,无论如何您都在使用真正的加密货币。

并添加我自己的推论:

  • 成功的 XSS 攻击可能会导致攻击者在您的客户端浏览器上执行代码,即使您使用的是 SSL - 所以即使您已经关闭了所有舱门,如果您的攻击者发现在其他人的浏览器上执行任何 javascript 代码的方法。

如果您打算使用 JavaScript 客户端,这会使许多 RESTful 身份验证方案变得不可能或愚蠢。来看看吧!

HTTP 基本身份验证

首先是 HTTP 基本身份验证。最简单的方案:只需在每个请求中传递一个名称和密码。

当然,这绝对需要 SSL,因为每次请求都会传递 Base64(可逆)编码的名称和密码。任何在线收听的人都可以轻松提取用户名和密码。大多数“基本身份验证不安全”的论点都来自“基于 HTTP 的基本身份验证”,这是一个糟糕的想法。

浏览器提供了内置的 HTTP Basic Auth 支持,但它很丑陋,您可能不应该将它用于您的应用程序。不过,另一种方法是将用户名和密码存储在 JavaScript 中。

这是最 RESTful 的解决方案。服务器不需要任何状态知识,并验证与用户的每一次交互。一些 REST 爱好者(主要是稻草人)坚持认为,维护任何一种状态都是异端邪说,如果您想到任何其他身份验证方法,就会在嘴边冒泡。这种标准合规性有理论上的好处——它由 Apache 开箱即用地支持——如果你愿意的话,你可以将你的对象作为文件存储在受 .htaccess 文件保护的文件夹中!

问题?您正在客户端缓存用户名和密码。这让 evil.ru 可以更好地破解它——即使是最基本的 XSS 漏洞也可能导致客户端将他的用户名和密码发送到恶意服务器。您可以尝试通过散列和加盐密码来减轻这种风险,但请记住:JavaScript Crypto is Hopeless。您可以通过将其留给浏览器的基本身份验证支持来减轻这种风险,但是......如前所述,丑陋至极。

HTTP 摘要验证

Is Digest authentication possible with jQuery?

更“安全”的身份验证,这是一个请求/响应哈希挑战。除了 JavaScript Crypto is Hopeless,它只在 SSL 上工作,你仍然需要在客户端缓存用户名和密码,这使得它比 HTTP Basic Auth 更复杂,但 没有更安全。

使用附加签名参数查询身份验证。

另一种更“安全”的身份验证,您可以在其中使用 nonce 和计时数据加密您的参数(以防止重复和计时攻击)并发送。最好的例子之一是 OAuth 1.0 协议,据我所知,这是在 REST 服务器上实现身份验证的一种非常棒的方式。

https://www.rfc-editor.org/rfc/rfc5849

哦,但是没有任何用于 JavaScript 的 OAuth 1.0 客户端。为什么?

JavaScript Crypto is Hopeless,记住。 JavaScript 无法在没有 SSL 的情况下参与 OAuth 1.0,并且您仍然必须在本地存储客户端的用户名和密码 - 这与 Digest Auth 属于同一类别 - 它比 HTTP Basic Auth 更复杂,但它并不安全.

令牌

用户发送用户名和密码,作为交换获得可用于验证请求的令牌。

这比 HTTP Basic Auth 稍微安全一些,因为一旦用户名/密码事务完成,您就可以丢弃敏感数据。它也不太 RESTful,因为令牌构成“状态”并使服务器实现更加复杂。

SSL 仍然

但问题在于,您仍然必须发送初始用户名和密码才能获得令牌。敏感信息仍会触及您的可妥协 JavaScript。

为了保护您的用户凭据,您仍然需要阻止攻击者访问您的 JavaScript,并且您仍然需要通过网络发送用户名和密码。需要 SSL。

令牌到期

强制执行令牌策略很常见,例如“嘿,当此令牌存在时间过长时,将其丢弃并让用户再次进行身份验证。”或“我很确定允许使用此令牌的唯一 IP 地址是 XXX.XXX.XXX.XXX”。其中许多政策都是非常好的想法。

火羊

但是,使用没有 SSL 的令牌仍然容易受到称为“sidejacking”的攻击:http://codebutler.github.io/firesheep/

攻击者没有获得您用户的凭据,但他们仍然可以伪装成您的用户,这可能非常糟糕。

tl;dr:通过网络发送未加密的令牌意味着攻击者可以轻松获取这些令牌并伪装成您的用户。 FireSheep 是一个让这一切变得非常简单的程序。

一个独立的、更安全的区域

您运行的应用程序越大,就越难绝对确保他们无法注入一些代码来改变您处理敏感数据的方式。你绝对信任你的 CDN 吗?你的广告商?你自己的代码库?

信用卡详细信息很常见,而用户名和密码不太常见 - 一些实施者将“敏感数据输入”保留在与其应用程序的其余部分不同的页面上,该页面可以尽可能严格控制和锁定,最好是一个难以钓鱼用户的。

Cookie(仅指代币)

将身份验证令牌放在 cookie 中是可能的(并且很常见)。这不会使用令牌更改任何 auth 的属性,它更方便。前面的所有论点仍然适用。

Session(仍然只是表示Token)

Session Auth 只是 Token 身份验证,但有一些差异使它看起来有点不同:

  • 用户从未经身份验证的令牌开始。
  • 后端维护一个与用户令牌相关联的“状态”对象。
  • 令牌在 cookie 中提供。
  • 应用程序环境从您那里抽象出细节。

除此之外,它与 Token Auth 并没有什么不同,真的。

这与 RESTful 实现相差甚远 - 使用状态对象,您将在有状态服务器上的普通 ol' RPC 路径上走得更远。

OAuth 2.0

OAuth 2.0 着眼于“软件 A 如何在软件 B 无法访问用户 X 的登录凭据的情况下让软件 B 访问用户 X 的数据”的问题。

实现只是用户获取令牌的标准方式,然后让第三方服务去“是的,这个用户和这个令牌匹配,你现在可以从我们这里获取他们的一些数据。”

不过,从根本上说,OAuth 2.0 只是一个令牌协议。它具有与其他令牌协议相同的属性 - 您仍然需要 SSL 来保护这些令牌 - 它只是改变了这些令牌的生成方式。

OAuth 2.0 可以通过两种方式帮助您:

  • 向其他人提供身份验证/信息
  • 从其他人那里获取身份验证/信息

但归根结底,你只是……使用令牌。

回到你的问题

所以,您要问的问题是“我应该将我的令牌存储在 cookie 中并让我的环境的自动会话管理处理细节,还是应该将我的令牌存储在 Javascript 中并自己处理这些细节?”

答案是:做任何让你开心的事

不过,关于自动会话管理的事情是,在幕后为您带来了很多神奇的事情。通常,自己控制这些细节会更好。

我 21 岁,所以 SSL 是的

另一个答案是:对任何事情都使用 https,否则盗贼会窃取您用户的密码和令牌。

【讨论】:

  • 很好的答案。我很欣赏令牌认证系统和基本 cookie 认证(通常内置在 Web 框架中)之间的等价性。这就是我一直在寻找的东西。我很感激你也涵盖了这么多潜在的问题以供考虑。干杯!
  • 我知道已经有一段时间了,但我想知道这是否应该扩展到包括 JWT? auth0.com/blog/2014/01/07/…
  • Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST 要求 服务器 是无状态的。存储在 客户端 的令牌不会以任何对服务器有意义的方式表示状态。 (2) 稍微复杂一点的服务器端代码与 RESTful 无关。
  • lol_nope_send_it_to_me_instead 我喜欢这个函数的名字:D
  • 您似乎忽略了一件事情:Cookie 在标记为 httpOnly 时是 XSS 安全的,并且可以通过安全和相同的站点进一步锁定。并且 cookie 处理已经存在了更长的时间 === 更加坚固。依靠 JS 和本地存储来处理令牌安全是一个傻瓜游戏。
【解决方案2】:

您可以通过使用 JWT (JSON Web Tokens) 和 SSL/HTTPS 来提高身份验证过程的安全性。

基本身份验证/会话 ID 可通过以下方式窃取:

  • MITM 攻击(中间人) - 没有 SSL/HTTPS
  • 入侵者访问用户的计算机
  • XSS

通过使用 JWT,您可以加密用户的身份验证详细信息并将其存储在客户端中,并将其与每个请求一起发送到 API,服务器/API 会在其中验证令牌。 没有私钥(服务器/API 秘密存储)就无法解密/读取 读取更新

新的(更安全的)流程是:

登录

  • 用户登录并将登录凭据发送到 API(通过 SSL/HTTPS)
  • API 接收登录凭据
  • 如果有效:
  • 在数据库中注册一个新会话读取更新
  • 使用私钥在 JWT 中加密用户 ID、会话 ID、IP 地址、时间戳等。
  • API 将 JWT 令牌发送回客户端(通过 SSL/HTTPS)
  • 客户端收到 JWT 令牌并存储在 localStorage/cookie 中

对 API 的每个请求

  • 用户向 API(通过 SSL/HTTPS)发送 HTTP 请求,并在 HTTP 标头中存储 JWT 令牌
  • API 读取 HTTP 标头并使用其私钥解密 JWT 令牌
  • API 验证 JWT 令牌,将 HTTP 请求中的 IP 地址与 JWT 令牌中的 IP 地址相匹配,并检查会话是否已过期
  • 如果有效:
  • 返回带有请求内容的响应
  • 如果无效:
  • 抛出异常 (403 / 401)
  • 标记系统中的入侵
  • 向用户发送警告电子邮件。

30.07.15 更新:

JWT 有效负载/声明实际上可以在没有私钥(秘密)的情况下读取,并且将其存储在 localStorage 中是不安全的。我对这些虚假陈述感到抱歉。但是他们似乎正在处理JWE standard (JSON Web Encryption)

我通过将声明(userID、exp)存储在 JWT 中来实现这一点,使用 API/后端只知道的私钥(秘密)对其进行签名,并将其作为安全的 HttpOnly cookie 存储在客户端上。这样它就不能通过 XSS 读取,也不能被操纵,否则 JWT 签名验证失败。此外,通过使用 secure HttpOnly cookie,您可以确保 cookie 仅通过 HTTP 请求(脚本无法访问)发送并且仅通过安全连接 (HTTPS) 发送。

17.07.16 更新:

JWT 本质上是无状态的。这意味着他们使自己无效/过期。通过在令牌的声明中添加 SessionID,您可以使其成为有状态的,因为它的有效性现在不仅取决于签名验证和到期日期,还取决于服务器上的会话状态。然而,好处是您可以轻松地使令牌/会话无效,这是以前使用无状态 JWT 无法做到的。

【讨论】:

  • 我认为,从安全的角度来看,JWT 仍然“只是一个令牌”。服务器仍然可以将用户 ID、IP 地址、时间戳等与不透明的会话令牌相关联,并且它不会比 JWT 更安全或更不安全。但是,JWT 的无状态特性确实使实现更容易。
  • @James JWT 的优点是可验证并且能够携带关键细节。这对于各种 API 场景非常有用,例如需要跨域身份验证的地方。会话不会那么好。它也是一个已定义(或至少正在进行中)的规范,对实现很有用。这并不是说它比任何其他好的令牌实现都好,但它定义明确且方便。
  • @Chris 是的,我同意你的所有观点。但是,由于使用了 JWT,上述答案中描述的流程本质上并不是一个更安全的流程。此外,除非您将标识符与 JWT 相关联并将状态存储在服务器上,否则 JWT 在上述方案中是不可撤销的。否则,您要么需要通过请求用户名/密码(糟糕的用户体验)定期获取新的 JWT,要么发出过期时间很长的 JWT(如果令牌被盗则不好)。
  • 我的回答不是 100% 正确,因为 JWT 实际上可以在没有私钥(秘密)的情况下进行解密/读取,并且将其存储在 localStorage 中并不安全。我通过将声明(userID、exp)存储在 JWT 中来实现这一点,使用 API/后端只知道的私钥(秘密)对其进行签名,并将其作为 HttpOnly cookie 存储在客户端上。这样它就不能被 XSS 读取。但是你必须使用 HTTPS,因为令牌可能会被 MITM 攻击窃取。我将更新我的答案以反映这一点。
  • @vsenko cookie 随来自客户端的每个请求一起发送。您不会从 JS 访问 cookie,它与从客户端到 API 的每个 HTTP 请求相关联。
【解决方案3】:

我会选择第二个,令牌系统。

您知道ember-authember-simple-auth 吗?它们都使用基于令牌的系统,例如 ember-simple-auth 状态:

用于实现基于令牌的轻量级且不显眼的库 Ember.js 应用程序中的身份验证。 http://ember-simple-auth.simplabs.com

它们具有会话管理功能,并且也很容易插入到现有项目中。

还有一个 Ember App Kit 示例版本的 ember-simple-auth:Working example of ember-app-kit using ember-simple-auth for OAuth2 authentication.

【讨论】:

    猜你喜欢
    • 2016-10-23
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-20
    • 2017-08-26
    相关资源
    最近更新 更多