REST身份验证和公开API密钥

时间:2011-03-29 12:41:37

标签: restful-authentication

我一直在阅读REST,关于它以及很多其他网站和博客都有很多问题。虽然我从来没有见过这个具体的问题...出于某种原因,我无法围绕这个概念进行思考......

如果我正在构建RESTful API,并且我想保护它,我看到的方法之一就是使用安全令牌。当我使用其他API时,有一个令牌和共享秘密......这是有道理的。我不明白的是,对休息服务操作的请求是通过javascript(XHR / Ajax)进行的,这是为了防止有人用FireBug(或浏览器中的“查看源代码”)这样简单的东西来嗅出它复制API密钥,然后使用密钥和秘密冒充该人?

6 个答案:

答案 0 :(得分:59)

我们正在公开合作伙伴只能在他们向我们注册的域名上使用的API。它的内容部分是公开的(但最好只在我们知道的域上显示),但对我们的用户来说大多是私有的。所以:

  • 要确定显示的是什么,我们的用户必须与我们一起登录,但这是单独处理的。

  • 要确定显示数据的 ,公共API密钥用于限制对我们所知道的域的访问,最重要的是确保私有用户数据不易受{{ 3}}。

此API密钥确实对任何人都可见,我们不会以任何其他方式验证我们的合作伙伴,我们CSRF。它仍然是安全的:

  1. 请求我们get-csrf-token.js?apiKey=abc123时:

    1. 在数据库中查找密钥abc123并获取该密钥的有效域列表。

    2. 查找CSRF验证cookie。如果它不存在,请生成一个安全的随机值并将其放在don't need REFERER会话cookie中。如果cookie确实存在,请获取现有的随机值。

    3. 从API密钥和Cookie中的随机值创建CSRF令牌,a HTTP-only。 (而不是在服务器上保留令牌列表,我们会对这些值进行签名。这两个值都可以在签名令牌中读取,这很好。)

    4. 将响应设置为不缓存,添加cookie,并返回如下脚本:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }
      
    5. 注意:

      • 上述内容不会阻止服务器端脚本伪造请求,但只能确保域与浏览器请求的匹配

      • sign it确保浏览器无法使用XHR(Ajax)加载然后检查JavaScript源。相反,常规浏览器只能使用<script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(或动态等效项)加载它,然后运行代码。当然,您的服务器应该支持same origin policy for JavaScript,也不应该为生成的JavaScript支持JSONP。

      • 在加载上述脚本之前,浏览器脚本可以更改document.domain的值。但是,相同的原始政策只允许通过删除前缀缩短域名,例如将subdomain.example.com重写为example.commyblog.wordpress.com改为wordpress.com,或者在某些浏览器中甚至是bbc.co.ukCross-Origin Resource Sharing

      • 如果使用某个服务器端脚本获取JavaScript文件,则服务器也将获取cookie。但是,第三方服务器无法使用户的浏览器将该cookie与我们的域相关联。因此,使用服务器端脚本获取的CSRF令牌和验证cookie只能由后续服务器端调用使用,而不能在浏览器中使用。但是,此类服务器端调用永远不会包含用户cookie,因此只能获取公共数据。这与服务器端脚本可以直接从合作伙伴的网站上获取的数据相同。

    6. 当用户登录时,以您喜欢的任何方式设置一些用户cookie。 (在请求JavaScript之前,用户可能已经登录。)

    7. 对服务器的所有后续API请求(包括GET和JSONP请求)必须包含CSRF令牌,CSRF验证cookie以及(如果已登录)用户cookie。服务器现在可以确定是否要信任该请求:

      1. 有效的CSRF令牌可确保JavaScript从预期的域加载, if 由浏览器加载。

      2. 没有验证Cookie的CSRF令牌的存在表明伪造。

      3. CSRF令牌和CSRF验证cookie的存在并不能确保任何事情:这可能是伪造的服务器端请求,也可能是来自浏览器的有效请求。 (它不是来自不受支持的域的浏览器的请求。)

      4. 用户cookie的存在确保用户登录,但不确保用户是给定合作伙伴的成员,也不确保用户正在查看正确的网站。

      5. 没有 CSRF验证Cookie的用户Cookie 的存在表明伪造。

      6. 用户cookie的存在确保当前请求通过浏览器进行。 (假设用户不会在未知网站上输入他们的凭据,并假设我们不关心用户使用他们自己的凭据来提出服务器端请求。)如果我们拥有CSRF验证cookie ,然后使用浏览器收到CSRF验证cookie。接下来,如果我们有一个带有有效签名的CSRF令牌, CSRF验证cookie中的随机数与该CSRF令牌中的随机数相匹配,那么就是那个在同一个早期请求期间也收到了令牌,在此期间设置了CSRF cookie,因此也使用了浏览器。这也意味着上述JavaScript代码在设置令牌之前执行,并且那时域对于给定的API密钥有效。

        所以:服务器现在可以安全地使用签名令牌中的API密钥。

      7. 如果服务器在任何时候不信任该请求,则返回403 Forbidden。窗口小部件可以通过向用户显示警告来响应。

    8. 不需要签署CSRF验证cookie,因为我们将其与签名的CSRF令牌进行比较。不签署cookie会使每个HTTP请求更短,服务器验证速度更快。

      生成的CSRF令牌无限期有效,但仅与验证cookie结合使用,因此在浏览器关闭之前一直有效。

      我们可以限制令牌签名的生命周期。我们可以在用户退出时删除CSRF验证cookie,以满足co.uk。并且为了不在多个合作伙伴之间共享每用户随机数,可以将API密钥添加到cookie名称。但即使这样,当请求新令牌时,也无法轻松刷新CSRF验证cookie,因为用户可能在多个窗口中浏览同一站点,共享一个cookie(在刷新时,将在所有窗口中更新,之后其他窗口中的JavaScript令牌将不再与该单个cookie匹配。)

      对于那些使用OAuth的人,请参阅the OWASP recommendation,我从中获得了JavaScript的想法。对于服务器端使用API​​,我们不能依赖JavaScript代码来限制域名,我们使用密钥而不是公共API密钥。

答案 1 :(得分:22)

api secret未明确传递,secret用于生成当前请求的符号,在服务器端,服务器生成符号后面的相同进程,如果两个符号匹配,然后成功验证请求 - 因此只有符号通过请求传递,而不是秘密。

答案 2 :(得分:9)

这个问题有一个公认的答案,但为了澄清,共享秘密身份验证的工作原理如下:

  1. 客户有公钥,这可以与任何人共享,不是 问题,所以你可以嵌入javascript。这用于标识服务器上的用户。
  2. 服务器有密钥,这个秘密必须受到保护。因此, 共享密钥身份验证要求您可以保护您的秘密 键。所以一个公共的javascript客户端直接连接到另一个 服务是不可能的,因为你需要一个服务器中间人 保护秘密。
  3. 服务器使用包含秘密的某种算法签署请求 密钥(密钥有点像盐),最好是时间戳然后将请求发送给服务。时间戳是为了防止&#34;重播&#34;攻击。请求的签名仅在 n 秒附近有效。您可以通过获取应包含签名中包含的时间戳值的时间戳标头来检查服务器上的内容。如果该时间戳已过期,则请求失败。
  4. 服务获取的请求不仅包含签名 还有用纯文本签名的所有字段。
  5. 然后,服务使用共享以相同的方式签署请求 密钥并比较签名。

答案 3 :(得分:1)

我认为你的意思是会话密钥而不是API密钥。该问题继承自http协议,称为Session hijacking。与任何网站一样,正常的“解决方法”是更改为https。

要运行REST服务安全,您必须启用https,并可能启用客户端身份验证。但毕竟,这超出了REST的想法。 REST从不谈论安全性。

答案 4 :(得分:1)

您要在服务器端执行的操作是生成一个过期的会话ID,该ID会在登录或注册时发送回客户端。 然后,客户端可以使用该会话ID作为共享密钥来签署后续请求。

会话ID只传递一次,必须通过SSL。

参见示例here

在签署请求时使用nonce和timestamp以防止会话劫持。

答案 5 :(得分:1)

我将尝试回答原始背景下的问题。所以问题是“秘密(API)密钥是否可以安全地放在JavaScript中。

在我看来,这是非常不安全的,因为它破坏了系统之间的身份验证的目的。由于密钥将暴露给用户,因此用户可以检索他/她未被授权的信息。因为在典型的休息通信中,身份验证仅基于API密钥。

我认为解决方案是JavaScript调用本质上将请求传递给负责进行休息调用的内部服务器组件。内部服务器组件假设Servlet将从安全源(例如基于权限的文件系统)读取API密钥,插入HTTP标头并进行外部休息调用。

我希望这会有所帮助。