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

Chr*_*ola 286 security authentication ember.js angularjs single-page-application

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

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

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

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

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

小智 451

这个问题已经以稍微不同的形式进行了详细讨论,在这里:

RESTful身份验证

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

Javascript Crypto是绝望的

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(可逆)编码的名称和密码.任何在线听的人都可以简单地提取用户名和密码.大多数"Basic Auth都是不安全的"论据来自"Basic Auth over HTTP",这是一个非常糟糕的主意.

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

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

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

HTTP摘要验证

使用jQuery可以进行摘要式身份验证吗?

一个更"安全"的身份验证,这是一个请求/响应哈希挑战.除了JavaScript Crypto是无望的,所以它只适用于SSL,你仍然需要在客户端缓存用户名和密码,使其比HTTP Basic Auth更复杂,但不再安全.

使用其他签名参数查询身份验证.

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

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

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

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

代币

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

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

SSL仍然

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

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

令牌到期

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

Firesheeping

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

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

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

一个独立,更安全的区域

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

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

Cookie(只是表示令牌)

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

会话(仍然仅仅意味着令牌)

会话身份验证只是令牌身份验证,但有一些差异使得它看起来有点不同:

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

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

这远远超出了RESTful实现 - 使用状态对象,您将继续沿着有状态服务器上的普通OL的路径前进.

OAuth 2.0

OAuth 2.0着眼于"软件A如何让软件B访问用户X的数据,而软件B无法访问用户X的登录凭据".

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

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

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

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

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

回到你的问题

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

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

然而,关于自动会话管理的事情是,幕后为你做了很多魔术.通常,自己控制这些细节会更好.

我21岁,所以SSL是肯定的

另一个答案是:使用https进行所有操作,或者强盗将窃取用户的密码和令牌.

  • 我知道已经有一段时间但是我想知道是否应该将其扩展到包括JWT?https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/ (11认同)
  • **Token**`它也不那么RESTful,因为令牌构成"状态并使服务器实现更复杂."(1)REST要求_server_是无状态的.存储的令牌_client-side_不以任何有意义的方式表示服务器的状态.(2)边缘化更复杂的服务器端代码与RESTfulness无关. (11认同)
  • `lol_nope_send_it_to_me_instead`我喜欢这个函数的名字:D (10认同)
  • 很好的答案.我很欣赏令牌身份验证系统和基本cookie身份验证(通常内置于Web框架中)之间的等效性.这就是我在寻找的东西.我很感谢你也考虑了很多潜在的问题.干杯! (3认同)
  • 终于找到了一个真正了解安全的人,通过阅读他解释的方式,做得好柯蒂斯. (3认同)
  • @ChrisNicola JWT基本上是上面**Token**方法的一个实现.相同的含义. (2认同)
  • 关于这个主题的答案应该使用SSL作为给定.列举SSL必须的原因,然后继续.没有它,没有什么是安全的.有很多关于保护服务的更深层次的主题,例如"通过GET与POST通过令牌(通过SSL)有什么安全问题?" 您的答案似乎使除SSL之外的所有方法无效,但大多数方法都是在SSL的基础上提出的. (2认同)
  • 您似乎忽略了一件事:Cookie标记为httpOnly时是XSS安全的,并且可以通过安全且相同的站点进一步锁定。Cookie的处理时间已久===更加艰苦。依靠JS和本地存储来处理令牌安全性是一个愚蠢的游戏。 (2认同)
  • 如果您使用 HTTP COOKIE 来存储您的令牌,那么 javascript 就无法接近它,从而增强安全性。但这仅在您的 API 和网站页面来自同一域时才有效:-) (2认同)

Gau*_*aui 55

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

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

  • MITM攻击(Man-In-The-Middle) - 没有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发送HTTP请求(通过SSL/HTTPS)
  • API读取HTTP标头并使用其私钥解密JWT标记
  • API验证JWT令牌,将HTTP请求中的IP地址与JWT令牌中的IP地址进行匹配,并检查会话是否已过期
  • 如果有效:
    • 返回响应请求的内容
  • 如果无效:
    • 抛出异常(403/401)
    • 标志侵入系统
    • 向用户发送警告电子邮件.

更新30.07.15:

实际上可以在没有私钥(秘密)的情况下读取JWT有效负载/声明,并且将其存储在localStorage中是不安全的.我很抱歉这些虚假陈述.但是他们似乎正在研究JWE标准(JSON Web加密).

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

更新17.07.16:

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

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

Del*_*ynx 7

我会去第二个,令牌系统.

您是否了解ember-authember-simple-auth?它们都使用基于令牌的系统,如ember-simple-auth状态:

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

他们有会话管理,也很容易插入现有项目.

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