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


307

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

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

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

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

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


我采用这种方式,stackoverflow.com
a/19820685/454252

Answers:


476

在这里,以略有不同的形式解决了这个问题:

RESTful身份验证

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

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身份验证”。

该浏览器提供了内置的HTTP Basic Auth支持,但它很丑陋,您可能不应该在应用程序中使用它。但是,替代方法是将用户名和密码隐藏在JavaScript中。

这是最RESTful的解决方案。服务器不要求任何状态知识,并验证与用户的每个单独交互。一些REST爱好者(大多数是稻草人)坚持认为,保持任何一种状态都是异端,如果您想到其他任何身份验证方法,它们都会在您的嘴里泛滥。这种标准的遵从性在理论上有好处-即装即用的Apache支持-如果您愿意,可以将对象作为文件存储在受.htaccess文件保护的文件夹中!

问题吗?您正在客户端缓存用户名和密码。这给了evil.ru一个更好的破解-甚至最基本的XSS漏洞都可能导致客户端将其用户名和密码发送到邪恶的服务器。您可以尝试通过对密码进行散列和加盐处理来减轻这种风险,但是请记住:JavaScript Crypto is Hopeless。您可以通过将其留给浏览器的基本身份验证支持来减轻这种风险,但是如前所述,这很丑陋。

HTTP摘要验证

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

更“安全”的身份验证,这是一个请求/响应哈希挑战。除了JavaScript Crypto是Hopeless之外,因此它只能在SSL上运行,并且您仍然必须在客户端缓存用户名和密码,这使其比HTTP Basic Auth更为复杂,但不再安全

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

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

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

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

记住,JavaScript加密是绝望的。没有SSL,JavaScript不能参与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的任何属性,这更方便了。前面所有的论点仍然适用。

会话(仍仅表示令牌)

会话身份验证只是令牌身份验证,但有一些区别,使它看起来似乎有点不同:

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

除此之外,实际上,它与令牌认证没有什么不同。

这从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,否则盗版者将窃取用户的密码和令牌。


3
好答案。我感谢令牌身份验证系统和基本cookie身份验证(通常内置于Web框架)之间的等效性。那就是我要找的东西。非常感谢您介绍了这么多潜在的问题供您考虑。干杯!
克里斯·尼古拉

11
我知道已经有一段时间了,但是我想知道是否应该将其扩展为包括JWT?auth0.com/blog/2014/01/07/…–
克里斯·尼古拉

14
令牌 It's also less RESTful, as tokens constitute "state and make the server implementation more complicated."(1)REST要求服务器无状态。对于服务器而言,令牌存储的客户端不会以任何有意义的方式表示状态。(2)稍微复杂一点的服务器端代码与RESTfulness无关。
soupdog 2016年

10
lol_nope_send_it_to_me_instead我喜欢这个函数的名称:D
Leo

6
您似乎忽略了一件事:Cookie标记为httpOnly时是XSS安全的,并且可以通过安全且相同的站点进一步锁定。Cookie的处理时间已久===更加艰苦。依靠JS和本地存储来处理令牌安全性是一个愚蠢的游戏。
马丁·彼得斯

57

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

15年7月30日更新:

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

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

更新了16.07.16:

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


1
最终,从安全的角度来看,JWT仍然只是“一个令牌”。服务器仍可以将用户ID,IP地址,时间戳等与不透明的会话令牌相关联,并且它的安全性与JWT差不多。但是,JWT的无状态性质确实使实现更加容易。
詹姆斯

1
@James JWT的优点是可验证并能够携带关键细节。这对于各种API场景(例如需要跨域身份验证的场景)非常有用。一个会议的东西不会那么好。它也是已定义(或至少正在进行中)的规范,对实现很有用。这并不是说它比任何其他好的令牌实现都要好,但是它定义明确且方便。
克里斯·尼古拉

1
@克里斯是的,我同意你的所有观点。但是,由于使用了JWT,以上答案中描述的流程本质上并不是要求更安全的流程。此外,除非您将标识符与JWT关联并将状态存储在服务器上,否则JWT在上述方案中不可撤销。否则,您要么需要通过请求用户名/密码来定期获取新的JWT(用户体验差),要么发出具有很长的到期时间的JWT(如果令牌被盗,则很糟糕)。
詹姆斯

1
我的回答不是100%正确,因为JWT实际上可以在没有私钥(秘密)的情况下解密/读取,并且将它存储在localStorage中并不安全。我通过在JWT中存储声明(用户ID,exp),使用私钥(秘密)对其进行签名来实现这一点,API /后端仅知道此信息并将其作为HttpOnly cookie存储在客户端上。这样,XSS无法读取它。但是您必须使用HTTPS,因为令牌可能会因MITM攻击而被盗。我将更新我的答案以对此进行思考。
盖伊2015年

1
@vsenko cookie与客户端的每个请求一起发送。您无需从JS访问Cookie,而是将其与从客户端到API的每个HTTP请求绑定在一起。
盖伊

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的工作示例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.