RESTful Web服务中的身份验证

时间:2012-11-01 22:26:11

标签: javascript web-services authentication

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

  1. 用户12345访问https://www.example.com/Login.htm并通过服务器进行身份验证(在我的情况下通过OpenID提供商)
  2. 用户12345然后访问页面https://www.example.com/Widgets.htm
  3. 服务器使用HTML页面和用于访问我的Web服务的JavaScript进行响应。
  4. 当加载HTML页面时,将调用javascript函数getWidgets()getWidgets()会致电我的网络服务https://www.example.com/Services/Widget/12345
  5. 服务响应用户窗口小部件列表,其他javascript函数renderWidgets(widgets)将更新html页面
  6. 我不希望除了用户12345以外的任何人访问他们自己的小部件,所以我想getWidgets()必须为我的网络服务提供一些身份验证。我不确定实现这一目标的最佳方法是什么。

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

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

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

    提前致谢。

3 个答案:

答案 0 :(得分:9)

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

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

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

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

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

    您的申请

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

    为什么? - 因为您真正要防范的是以下内容:

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

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

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

    1. 为您的服务器启用SSL。
    2. 仅允许/auth/login(或任何您的登录POST方法)通过SSL发出请求(在C#中,这可以通过使用方法或控制器上的[RequireHttps]属性来完成)。
    3. 拒绝任何源自您自己域名的AJAX请求。
    4. 在Cookie中使用加密层。
    5. 您的Cookie应包含哪些内容?

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

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

      1. 如果cookie存在,请尝试使用您的私人密码对其进行解密。
      2. 如果生成的解密数据是垃圾 - 抛出401: Unauthorized响应(这是一个更改或伪造的cookie)
      3. 如果生成的解密数据是与您的数据库匹配的用户名 - 您现在知道谁在提出请求 - 并且可以相应地过滤/提供数据。
      4. 我希望这会有所帮助。 :)如果没有 - 随意发表任何评论并提出问题,我会尽力澄清。

答案 1 :(得分:2)

这不像将请求发送到Web服务那么简单吗?

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

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

因此,我们使用了服务器设置的共享密钥并发送到客户端作为生成散列消息授权代码(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。他们使用的Hash是MD5,查询字符串包含在要进行哈希处理的正文中,而秘密则使用从初始身份验证调用中获取的会话密钥。

答案 2 :(得分:-1)

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

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