12

我目前正在创建一个网站,用户可以查看和修改他们的小部件。与存储在我的服务器上的小部件数据的所有交互都将通过 RESTful Web 服务完成。例如,如果用户想要查看他们的小部件列表,则执行流程将类似于:

  1. 用户 12345 访问https://www.example.com/Login.htm服务器并对其进行身份验证(在我的情况下通过 OpenID 提供程序)
  2. 用户 12345 然后访问该页面https://www.example.com/Widgets.htm
  3. 服务器以 HTML 页面和 javascript 响应,用于访问我的 Web 服务。
  4. 当 HTML 页面加载后,getWidgets()将调用 javascript 函数。getWidgets()将调用我的网络服务https://www.example.com/Services/Widget/12345
  5. 该服务以用户小部件列表进行响应,另一个 javascript 函数renderWidgets(widgets)将更新 html 页面

除了用户 12345 之外,我不希望其他任何人访问他们自己的小部件,所以我想getWidgets()必须为我的 Web 服务提供一些身份验证。我不确定实现这一目标的最佳方法是什么。

我在想客户端和服务器可以有一个共享的秘密,getWidgets()将发送到网络服务。服务器可以将这个秘密生成为随机字符串(数字、GUID 或其他),并在客户端请求初始 HTML 页面时将其包含在响应标头中。客户端在向服务器发送请求时将使用此密钥。

这听起来像一个明智的想法吗?

这是一个常见的要求,那么是否有实现相同目标的标准方法?据我所知,这超出了 OpenID 的范围,OAuth 不适合。

提前致谢。

4

3 回答 3

9

这是一个很好的问题 - 但我认为您的解决方案可能需要比您想象的更复杂一些。

通常,您希望对这种场景进行身份验证的方式是两阶段握手。第一步是让您的应用程序向服务器提供一个私钥(由服务器生成,对客户端应用程序唯一)来验证它实际上是一个有效的客户端。这为您的服务器提供了权威证据,证明请求来自它知道并可以信任的软件。

然后,第二步是当用户登录到您的客户端应用程序时,他们提供用户名/密码组合。此信息以及您的应用程序密钥都应通过 SSL 发送到服务器。

SSL 对数据进行加密,因此具有数据包嗅探器的第三方无法读取传输中的数据,服务器执行以下操作:

  1. 检查应用程序密钥是否有效。
  2. 验证用户名是否存在,并且与应用程序相关联。
  3. 加密密码并根据数据库中与用户名关联的加密版本测试加密版本。
  4. 如果上面列出的所有检查都通过,服务器会返回一个会话 ID,该 ID 可以放入客户端 cookie - 并用于在每个后续请求中重新验证用户。如果任何测试失败 - 服务器返回401: Unauthorized响应或其他类似错误。

此时,客户端可以使用返回的会话 ID,而无需继续重新提交应用程序密钥。

你的申请

现在,在您的情况下,您实际上可能将客户端/服务器托管在同一应用程序和同一服务器上。在这种情况下 - 您通常可以跳过所有围绕私有应用程序密钥的部分 - 并简单地禁止跨站点脚本请求。

为什么?- 因为您真正要防止的事情是:

服务器 A 托管您的 RESTful API。客户端的 B、C 和 D 托管依赖于服务器 A 的 API 的客户端。您不希望客户端 E(不是您的应用程序 - 并且是恶意的)能够通过绕过或窃取其他客户端之一的凭据来访问服务器 A。

但是,如果客户端和服务器都托管在同一个地方,因此具有相同的 URL - 即 RESTful API 驻留,www.yourdomain.com/api客户端驻留www.yourdomain.com/- 您通常可以不允许任何源自外部的 AJAX 类型请求yourdomain.com- 并且那是您的安全层。

在这种情况下,您只需执行以下操作即可获得合理的安全级别:

  1. 为您的服务器启用 SSL。
  2. 只允许通过 SSL 发出请求/auth/login(或任何您的登录POST方法)(在 C# 中,这可以通过使用[RequireHttps]方法或控制器上的属性来完成)。
  3. 拒绝任何源自您自己域之外的 AJAX 请求。
  4. 在您的 cookie 中使用一层加密。

你的 cookie 应该包含什么?

理想情况下,cookie 应该包含只有您的服务器才能解密的双向加密数据。换句话说 - 您可能会在 cookie 中放置类似用户usernameuser_id内部的内容 - 但使用 Rijndael 或其他加密系统对其进行 2 路加密 - 使用只有您的服务器可以访问的加密密码(我建议使用随机字符串)。

然后 - 当您收到附加 cookie 的后续请求时,您可以简单地执行以下操作:

  1. 如果 cookie 存在,请尝试使用您的私人密码对其进行解密。
  2. 如果生成的解密数据是垃圾 - 抛出401: Unauthorized响应(这是一个更改或伪造的 cookie)
  3. 如果生成的解密数据是与您的数据库匹配的用户名 - 您现在知道是谁在发出请求 - 并且可以相应地过滤/提供数据。

我希望这有帮助。:) 如果没有 - 请随时发表任何评论并提出问题,我会尽力澄清。

于 2012-11-01T23:36:58.773 回答
2

这不是将请求 MAC 化到 Web 服务那么简单吗?

因此,您提供了调用 Web 服务的 JavaScript,在此 JavaScript 中放置了一个 nonce。与大多数 nonce 实现不同,您可以在用户会话期间使用它。

调用 Web 服务时,GetWidgets() 中的 JavaScript 使用 nonce 作为键来散列一些“随机”数据,我可能会使用格式化为字符串的时间和数据,用户的 id (12345) 作为盐. JavaScript 然后将散列数据和未散列的时间字符串作为 Web 服务调用的一部分发送,但不发送随机数。然后,我会确保发送的时间是最近的(即最后 20 秒,限制重播攻击),并且当服务器将用户 ID 作为盐与随机数(它知道因为它设置它)散列时间时,它得到相同的结果。

因此,我们使用了由服务器设置并发送给客户端的共享密钥,作为生成散列消息授权码(MAC 或 HMAC)的密钥,同时也防止了重放攻击(每个请求的 MAC 都不同,并且有一个非常短的重播窗口)并通过不在 cookie 中传输任何会话信息来防止会话劫持。


我以前没有遇到过您的具体情况,但它似乎确实是身份验证消息的一般问题的具体情况,但不想随每条消息发送凭据。MACing 是我所看到的可靠方式。

请参阅:http ://en.wikipedia.org/wiki/Message_authentication_code ,这很好地概述了 MACing,甚至包括一个很好的图表来帮助解释。这是一个相当成熟的解决方案,我建议的唯一偏差(因为我已经犯规了)是在消息中包含时间以防止重播。

它是 Last.fm 用于帮助授权访问其 API 的方法的基础,请参阅此页面上的第 8 部分(签名调用):http ://www.last.fm/api/authspec#8 。他们使用的散列是 MD5,查询字符串包含在要散列的正文中,使用的秘密是从初始身份验证调用获得的会话密钥。

于 2012-11-29T19:32:47.823 回答
-1

我对安全性了解不多,但我认为这完全取决于您愿意花费多少时间/成本(请记住,一切都是可破解的)。

像您一样关心安全性,您可能保护了会话变量,您可以做的最简单的事情是对服务器操作的 ajax 调用,您可以在其中检查会话并将其与用户请求进行比较。

于 2012-12-02T14:09:54.077 回答