REST身份验证并公开API密钥


93

我一直在阅读REST,因此有很多关于REST的问题,以及许多其他站点和博客。尽管我从未见过这个特定的问题...由于某种原因,我无法将这个想法笼罩在心...

如果我正在构建RESTful API,并且想对其进行保护,那么我所看到的方法之一就是使用安全性令牌。当我使用其他API时,会有一个令牌和一个共享的秘密……很有意义。我不明白的是,正在通过javascript(XHR / Ajax)发出对REST服务操作的请求,这是为了防止某人通过诸如FireBug(或浏览器中的“查看源代码”)之类的简单内容来嗅探到这一点,以及复制API密钥,然后假冒使用密钥和机密的那个人?


我见过的一种方法是使用安全性令牌,实际上有很多方法。有一个具体的例子。我可能会认为您对“ REST”和“仅向注册用户提供javascript API”感到困惑(例如Google Maps)。
PeterMmm 2011年

1
自从您将近两年前问:您最终使用了什么?
Arjan

我实际上并没有使用任何东西,我只是想把头放在创建概念上。PeterMmm的上述评论可能是正确的...仍然不需要实施任何此类操作,但我想改善自己...感谢后续。
tjans 2013年

Answers:


22

api secret没有显式传递,secret用于生成当前请求的标志,在服务器端,服务器按照相同的过程生成标志,如果两个标志匹配,则请求成功通过身份验证-因此仅签名是通过请求传递的,而不是秘密。


9
因此,如果只是经过的信号...在javascript中还没有暴露出来...所以,如果我通过其API(由javascript调用)在我的网页上放置了一张闪烁的照片,而您访问了我的网页,我是否向访问我页面的任何人公开我的API密钥?
tjans 2011年

6
我认为我没有正确地问我的问题……这可能是我没有找到我要找的东西的部分原因。当我进行ajax调用时,比如说使用jquery,我必须将api密钥嵌入到ajax调用中,以便将其传递给服务器...在那时,有人可以看到API密钥。如果我理解那是错误的,如果未将API密钥嵌入到客户端脚本中,该API密钥将如何与请求一起发送?
tjans 2011年

4
结论:在使用openapi / restapi之前,将为人们分配apikey + apisecret对,apikey +符号将被转移到服务器端以确保服务器知道谁在发出请求,而apisecret将永远不会转移到服务器端以确保安全。
James.Xu 2011年

7
因此,@ James.Xu的“秘密用于生成当前请求的标志”的声明为FALSE!因为客户端不知道秘密,因为将其发送给他是不安全的(他还怎么知道?)“秘密”在技术上是“私钥”,仅由服务器使用(因为没人知道)生成一个与客户的标牌进行比较的标牌。那么问题来了:什么样的数据与客户端和服务器以外的其他人不知道的“ api密钥”结合在一起?Sign = api_key +什么?
AC

1
你说得对,@ ACs。即使两个服务器(网站和第三方API)都知道相同的秘密,也无法在网站服务器上计算出某些签名,然后将其结果放入HTML / JavaScript中,然后使浏览器将其传递给API。这样做,其他任何服务器都可以从第一个Web服务器请求HTML,从响应中获取签名,然后在自己网站的HTML中使用该签名。(我真的认为以上文章并未回答有关HTML中公共API密钥如何安全的问题。)
Arjan 2014年

61

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

  • 要确定什么是所示,我们的用户必须和我们一起登录,但这是分开处理。

  • 要确定其中显示的数据,公共API密钥是用来限制进入我们知道域,首先要保证用户私有数据不会受到CSRF

该API密钥的确对所有人可见,我们不会以任何其他方式对合作伙伴进行身份验证,并且不需要REFERER。仍然是安全的:

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

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

    2. 查找CSRF验证cookie。如果不存在,则生成一个安全的随机值,并将其放入仅HTTP的会话cookie中。如果cookie存在,则获取现有的随机值。

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

    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.');
      }

    笔记:

    • 上面的内容不会阻止服务器端脚本伪造请求,而只是确保域匹配 ,如果浏览器请求。

    • JavaScript相同原始策略可确保浏览器无法使用XHR(Ajax)加载并检查JavaScript源。相反,常规浏览器只能使用<script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(或动态等效项)加载它,然后运行代码。当然,你的服务器应该支持跨来源资源共享,也为JSONP生成的JavaScript。

    • 浏览器脚本可以document.domain在加载上述脚本之前更改的值。但是相同的原始策略仅允许通过删除前缀来缩短域,例如重写subdomain.example.com为just example.commyblog.wordpress.comto wordpress.com,或者在某些浏览器中甚至重写bbc.co.ukco.uk

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

  2. 用户登录时,可以根据自己的喜好设置一些用户cookie。(在请求JavaScript之前,用户可能已经登录。)

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

    1. 有效的CSRF令牌的存在可确保从预期的域中加载JavaScript, ,如果装载由浏览器。

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

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

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

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

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

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

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

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

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

我们可以限制令牌签名的寿命。当用户注销时,我们可以删除CSRF验证cookie,以满足OWASP的建议。为了避免在多个合作伙伴之间共享每个用户的随机数,可以将API密钥添加到cookie名称中。但是即使这样,当请求新令牌时,也无法轻易刷新CSRF验证cookie,因为用户可能会在多个窗口中浏览同一站点,共享一个cookie(刷新时,它将在所有窗口中更新,此后,其他窗口中的JavaScript令牌将不再与该单个Cookie匹配)。

对于使用OAuth的用户,另请参阅OAuth和客户端小部件,我从中获得了JavaScript想法。对于服务器端使用的API,我们不能依靠JavaScript代码来限制域,我们使用的是私钥而不是公共API密钥。


1
使用CORS时,也许可以放心地扩展它。代替上面的方法,当OPTIONS使用URL中的一些公共API密钥处理预处理的请求时,服务器可能会告诉浏览器允许使用哪些域(或取消请求)。请注意,有些请求不需要预先处理的请求,或者根本不使用CORS,并且CORS需要IE8 +。如果IE7使用了某些Flash后备广告,那么可能会有一些动态功能crossdomain.xml可以帮助实现这一目标。我们尚未尝试CORS / Flash。
Arjan

10

这个问题的答案是可以接受的,但为了澄清起见,共享机密身份验证的工作方式如下:

  1. 客户端具有公共密钥,可以与任何人共享,没关系,因此您可以将其嵌入javascript。这用于标识服务器上的用户。
  2. 服务器具有密钥,此密钥必须受到保护。因此,共享密钥身份验证要求您可以保护您的秘密密钥。因此,无法直接连接到其他服务的公共javascript客户端,因为您需要服务器中间人来保护机密。
  3. 服务器使用某种算法来签署请求,该算法包括秘密密钥(秘密密钥有点像盐),最好是时间戳,然后将请求发送到服务。时间戳是为了防止“重播”攻击。请求的签名仅在n左右有效秒钟。您可以通过获取时间戳报头来检查服务器上的内容,该报头应包含签名中包含的时间戳值。如果该时间戳记过期,则请求失败。
  4. 该服务获取的请求不仅包含签名,而且还包含以纯文本签名的所有字段。
  5. 然后,该服务使用共享密钥以相同的方式对请求进行签名,并比较签名。

没错,但在设计你的答案并没有暴露API密钥。但是,在某些API中,API密钥公开可见的,这就是问题所在:“对通过javascript(XHR / Ajax)进行的REST服务操作的请求”。(我认为,已经接受的答案也是错误的;很好,你的观点2很清楚。)
Arjan 2014年

1

我想你的意思是会话密钥而不是API密钥。该问题是从http协议继承的,称为会话劫持。如同在任何网站上一样,通常的“解决方法”是更改为https。

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


8
实际上,我确实是指钥匙。如果我没记错的话,要使用API​​,您要将API密钥和机密传递给其余服务以进行身份​​验证,对吗?我知道一旦通过电线将其通过SSL加密,但是在发送之前,使用它的客户端代码可以完全看到它……
tjans 2011年

1

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

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

看例子 在这里

对请求进行签名时,请使用随机数和时间戳,以防止会话劫持。


1
但是,当第三方使用您的API 时如何进行登录?如果用户要登录,那么事情很简单:只使用一个会话?但是,当其他网站需要通过您的API进行身份验证时,这无济于事。(此外,这闻起来像是在推广您的博客。)
Arjan

1

我将尝试在原始上下文中回答该问题。因此,问题是“可以安全地在JavaScript中放置秘密(API)密钥。

我认为这非常不安全,因为它破坏了系统之间进行身份验证的目的。由于密钥将向用户公开,因此用户可以检索未经授权的信息。因为在典型的休息通信中,身份验证仅基于API密钥。

我认为一个解决方案是JavaScript调用实质上将请求传递给内部服务器组件,该内部服务器组件负责执行rest调用。假设内部服务器组件是一个Servlet,它将从受保护的源(例如基于权限的文件系统)中读取API密钥,插入到HTTP标头中并进行外部rest调用。

我希望这有帮助。

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.