认证和会话管理的SPA最佳实践

在使用Angular、Ember、React等框架构建SPA风格的应用程序时,人们认为哪些是认证和会话管理的最佳实践?我能想到处理这个问题的几种方法。

  1. 将其与常规web应用程序的身份验证区别对待,假设API和和UI具有相同的起源域。

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

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

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

143470 次浏览

我会选择第二种,代币系统。

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

用于实现基于令牌的轻量级且不引人注目的库 在Ember.js应用程序中的身份验证。 http://ember-simple-auth.simplabs.com < / p >

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

还有Ember App Kit的示例版本Ember -simple-auth: 使用ember-simple-auth进行OAuth2身份验证的ember-app-kit的工作示例。

这个问题已经在这里以一种稍微不同的形式详细地讨论过:

RESTful Authentication . sh RESTful Authentication

但这是从服务器端解决的。让我们从客户端来看这个问题。在此之前,我们有一个重要的前奏:

Javascript加密是没有希望的

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_basic Auth”;这是个糟糕的主意。

浏览器提供了内置的HTTP基本认证支持,但它很难看,你可能不应该在你的应用程序中使用它。不过,另一种选择是将用户名和密码保存在JavaScript中。

这是最rest化的解决方案。服务器不需要任何状态知识,并对与用户的每个交互进行身份验证。一些REST爱好者(主要是稻草人)坚持认为,维护任何类型的状态都是异端邪说,如果您想到任何其他身份验证方法,他们就会大呼过瘾。这种符合标准的做法在理论上是有好处的——Apache已经开箱即用了——如果你愿意,你可以把对象作为文件存储在。htaccess文件保护的文件夹中!

问题吗?您在客户端缓存用户名和密码。这给了evil.ru一个更好的破解方法——即使是最基本的XSS漏洞也可能导致客户端将他的用户名和密码发送给邪恶服务器。您可以尝试通过哈希和盐水密码来降低这种风险,但请记住:JavaScript加密是没有希望的。你可以通过把它留给浏览器的基本认证支持来减轻这种风险,但是..如前所述,丑如罪恶。

HTTP摘要认证

是否可以使用jQuery进行文摘认证?< / >

更“安全”的;Auth,这是一个请求/响应哈希挑战。除了JavaScript加密是没有希望的,所以它只在SSL上工作,你仍然必须在客户端缓存用户名和密码,使它比HTTP基本认证更复杂,但不再安全

查询带有附加签名参数的认证。

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

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

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

JavaScript加密是没有希望的,记住。JavaScript不能在没有SSL的情况下参与OAuth 1.0,你仍然必须在本地存储客户端的用户名和密码——这与摘要认证属于同一类别——它比HTTP基本认证更复杂,但它是不再安全

令牌

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

这比HTTP Basic Auth稍微安全一些,因为只要用户名/密码事务完成,您就可以丢弃敏感数据。它的rest性也较差,因为符号构成了“状态”;并使服务器实现更加复杂。

SSL仍然

但问题是,您仍然必须发送初始用户名和密码才能获得令牌。敏感信息仍然会涉及到易受攻击的JavaScript。

为了保护用户的凭据,仍然需要使攻击者远离JavaScript,并且仍然需要通过网络发送用户名和密码。SSL所需。

令牌到期

强制执行令牌策略是很常见的,比如“嘿,当这个令牌存在太久了,丢弃它,让用户重新验证”。或“我非常确定允许使用此令牌的唯一IP地址是__abc0”;这些政策中有许多都是非常好的想法。

firesheep

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

攻击者无法获得用户的凭据,但他们仍然可以假装是您的用户,这可能非常糟糕。

tl;dr:通过网络发送未加密的令牌意味着攻击者可以很容易地获取这些令牌并假装是您的用户。FireSheep是一个很简单的程序。

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

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

信用卡详细信息常见,用户名和密码不常见——一些实施者将“敏感数据输入”与应用程序的其他部分分开保存在一个单独的页面上,这个页面可以尽可能严格地控制和锁定,最好是一个难以钓鱼用户的页面。

Cookie(只是表示令牌)

将身份验证令牌放在cookie中是可能的(也是常见的)。这不会改变令牌认证的任何属性,这更像是一件方便的事情。上述所有论点仍然适用。

Session(仍然只是Token)

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

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

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

这甚至偏离了RESTful实现——使用状态对象,您将越来越接近有状态服务器上普通RPC的路径。

OAuth 2.0

OAuth 2.0着眼于这样的问题:“软件A如何让软件B访问用户X的数据,而软件B却不能访问用户X的登录凭证。”

这个实现在很大程度上只是一个用户获得令牌的标准方式,然后第三方服务就会说“是的,这个用户和这个令牌匹配,你现在可以从我们这里获得他们的一些数据。”

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

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

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

但说到底,你只是…使用令牌。

回到你的问题

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

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

不过,关于自动会话管理的事情是,在幕后有很多神奇的事情发生。通常,自己掌控这些细节会更好。

我21岁,所以SSL是肯定的

另一个答案是:一切都使用https,否则强盗会窃取用户的密码和令牌。

您可以通过使用JWT (JSON Web令牌)和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的每个请求

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

更新30.07.15:

JWT有效载荷/声明实际上可以在没有私钥(秘密)的情况下读取,并且将其存储在localStorage中是不安全的。我为这些虚假的陈述感到抱歉。然而,他们似乎在JWE标准(JSON Web加密)工作。

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

更新17.07.16:

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