SPA身份验证和会话管理的最佳实践

时间:2014-01-07 03:13:49

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

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

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

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

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

  3. 我在这里并不是一位专家,但对于绝大多数情况来说,#1似乎已经足够了,但我真的希望听到一些更有经验的意见。

3 个答案:

答案 0 :(得分:425)

这个问题的解决方式略有不同,详见:

RESTful Authentication

但这是从服务器端解决的。让我们从客户端看一下这个。然而,在我们这样做之前,这是一个重要的前奏:

Javascript加密是无望的

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

http://www.matasano.com/articles/javascript-cryptography/

总结:

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

并添加我自己的推论:

  • 成功的XSS攻击可能导致攻击者在您的客户端浏览器上执行代码,即使您正在使用SSL - 所以即使您已经完成了每个孵化,您的浏览器也会加密如果您的攻击者找到了在其他人的浏览器上执行任何JavaScript代码的方法,那么仍然会失败。

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

HTTP Basic Auth

首先,HTTP Basic Auth。最简单的方案:只需为每个请求传递一个名称和密码。

当然,这绝对需要SSL,因为您在每次请求时都会传递Base64(可逆)编码的名称和密码。任何在线听的人都可以简单地提取用户名和密码。大多数&#34; Basic Auth都是不安全的&#34;争论来自&#34;基本身份验证通过HTTP&#34;这是一个糟糕的主意。

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

这是最RESTful的解决方案。服务器不需要任何状态知识,并且验证与用户的每个单独交互。一些REST爱好者(大多数是稻草人)坚持认为维持任何形式的状态都是异端邪说,如果你想到任何其他认证方法,它会在嘴里发泡。这种标准兼容性有理论上的好处 - 它是开箱即用的 - 它可以将您的对象存储在受.htaccess文件保护的文件夹中,如果您愿意的话!

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

HTTP摘要验证

Is Digest authentication possible with jQuery?

更多&#34;安全&#34; auth,这是一个请求/响应哈希挑战。除了 JavaScript Crypto是绝望的,所以它只适用于SSL,你仍然需要在客户端缓存用户名和密码,使其比HTTP Basic Auth更复杂,但不再安全< / em>的

使用其他签名参数进行身份验证。

另一个&#34;安全&#34; auth,用nonce和timing数据加密你的参数(以防止重复和定时攻击)并发送。其中一个最好的例子是OAuth 1.0协议,据我所知,这是一种在REST服务器上实现身份验证的非常好的方法。

http://tools.ietf.org/html/rfc5849

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

JavaScript加密无望,请记住。 JavaScript无法在没有SSL的情况下参与OAuth 1.0,您仍然需要在本地存储客户端的用户名和密码 - 这与Digest Auth属于同一类别 - 它比HTTP更复杂基本身份验证,但不再安全

令牌

用户发送用户名和密码,并在交换中获取可用于验证请求的令牌。

这比HTTP Basic Auth稍微安全一些,因为只要用户名/密码事务完成,您就可以丢弃敏感数据。它也不那么RESTful,因为令牌构成了状态&#34;并使服务器实现更复杂。

SSL仍然

但是,您仍需要发送初始用户名和密码才能获得令牌。敏感信息仍然触及您可妥协的JavaScript。

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

令牌到期

执行令牌政策很常见,例如&#34;嘿,当此令牌已经存在太久时,丢弃它并让用户再次进行身份验证。&#34;或者&#34;我非常确定允许使用此令牌的唯一IP地址是XXX.XXX.XXX.XXX&#34;。其中许多政策都是非常好的想法。

Firesheeping

但是,使用不使用SSL的令牌仍然容易受到名为“劫持”的攻击:http://codebutler.github.io/firesheep/

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

tl; dr:通过电线发送未加密的令牌意味着攻击者可以轻易地抓住这些令牌并伪装成您的用户。 FireSheep是一个让这很容易的程序。

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

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

信用卡详细信息的常见信息,用户名和密码不太常见 - 某些实施者会保留敏感数据输入信息。在与其应用程序的其余部分分开的页面上,可以尽可能严格控制和锁定的页面,最好是难以用户使用的页面。

Cookie(仅表示令牌)

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

会话(仍然只是表示令牌)

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

  • 用户以未经身份验证的令牌开头。
  • 后端维持一个状态&#39;与用户的令牌绑定的对象。
  • 令牌以cookie形式提供。
  • 应用程序环境将详细信息从您身上抽象出来。

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

这远远超出了RESTful实现 - 随着状态对象,你越来越走在普通的路径上了。有状态服务器上的RPC。

OAuth 2.0

OAuth 2.0着眼于问题&#34;如果软件B无法访问用户X的登录凭据,软件A如何让软件B访问用户X的数据。&#34; < / p>

实现只是用户获取令牌的标准方式,然后是第三方服务,这个用户和此令牌匹配,您可以获得他们的一些数据现在来自我们。&#34;

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

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

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

但是当它归结为它时,你只是......使用令牌。

回到你的问题

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

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

关于自动会话管理的问题在于,幕后为你做了很多魔术。通常,自己控制这些细节会更好。

我21岁,所以SSL是肯定的

另一个答案是:对所有内容使用https,否则强盗会窃取您的用户&#39;密码和令牌。

答案 1 :(得分:49)

您可以使用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 standard (JSON Web Encryption)

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

已更新17.07.16:

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

答案 2 :(得分:7)

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

您是否了解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.