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

Treat it no differently than authentication with a regular web application assuming the API and and UI have the same origin domain. This would likely involve having a session cookie, server side session storage and probably some session API endpoint that the authenticated web UI can hit to get current user information to help with personalization or possibly even determining roles/abilities on the client side. The server would still enforce rules protecting access to data of course, the UI would just use this information to customize the experience. Treat it like any third-party client using a public API and authenticate with some sort of token system similar to OAuth. This token mechanism would used by the client UI to authenticate each and every request made to the server API.

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


当前回答

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

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

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

通过使用JWT,您将加密用户的身份验证细节并存储在客户端中,并将其与每个请求一起发送到API,在那里服务器/API验证令牌。它不能解密/读取没有私钥(服务器/API秘密存储)读取更新。

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

登录

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

对API的每个请求

用户向API发送HTTP请求(通过SSL/HTTPS),并在HTTP报头中存储JWT令牌 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之前是无法做到的。

其他回答

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

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

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

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

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

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

宁静的身份验证

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

Javascript加密是没有希望的

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

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

总结:

中间人攻击可以简单地使用<script>函数hash_algorithm(password){lol_nope_send_it_to_me_instead(password);} > < /脚本 中间人攻击对于通过非ssl连接为任何资源提供服务的页面来说是微不足道的。 一旦你有了SSL,你就使用了真正的加密。

再加上我自己的推论:

一次成功的XSS攻击可能会导致攻击者在您的客户端浏览器上执行代码,即使您使用的是SSL——因此,即使您已经做好了所有准备,如果攻击者找到了在其他人的浏览器上执行任何javascript代码的方法,您的浏览器加密仍然可能失败。

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

HTTP基本认证

首先,也是最重要的,HTTP基本认证。最简单的方案:在每个请求中传递一个名称和密码。

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

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

This is the most RESTful solution. The server requires no knowledge of state whatsoever and authenticates every individual interaction with the user. Some REST enthusiasts (mostly strawmen) insist that maintaining any sort of state is heresy and will froth at the mouth if you think of any other authentication method. There are theoretical benefits to this sort of standards-compliance - it's supported by Apache out of the box - you could store your objects as files in folders protected by .htaccess files if your heart desired!

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

HTTP摘要认证

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

一个更“安全”的认证,这是一个请求/响应哈希挑战。除了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地址是XXX.XXX.XXX.XXX”。这些政策中有许多都是非常好的想法。

firesheep

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

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

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

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

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

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

Cookie(只是表示令牌)

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

Session(仍然只是Token)

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

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

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

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

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接收登录凭据 如果有效: 在数据库中注册新会话Read update 加密JWT中的用户ID、会话ID、IP地址、时间戳等,并使用私钥。 API将JWT令牌发送回客户端(通过SSL/HTTPS) 客户端接收JWT令牌并存储在localStorage/cookie中

对API的每个请求

用户向API发送HTTP请求(通过SSL/HTTPS),并在HTTP报头中存储JWT令牌 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之前是无法做到的。