REST身份validation和公开API密钥
我一直在阅读REST,关于它的问题以及其他许多网站和博客上都有很多问题。 虽然我从来没有见过这个具体的问题…出于某种原因,我无法围绕这个概念…
如果我正在构build一个RESTful API,并且我想要保护它,那么我所看到的方法之一就是使用安全令牌。 当我使用其他API时,有一个令牌和一个共享的秘密…是有道理的。 我不明白的是,正在通过javascript(XHR / Ajax)来完成对rest服务操作的请求,防止有人用FireBug(或者浏览器中的“查看源代码”)等简单的东西来嗅探出来复制API密钥,然后使用密钥和秘密冒充该人员?
api秘密没有被明确的传递,秘密被用来产生一个当前请求的标志 ,在服务器端,服务器在相同的进程之后产生标志,如果两个标志匹配,那么请求被authentication成功 – 所以只有标志是通过请求,而不是秘密。
我们正在公开一个合作伙伴只能在他们向我们注册的域名上使用的API。 它的内容部分是公开的(但最好只在我们所知道的领域上显示),但是对我们的用户来说大多是私有的。 所以:
-
要确定显示的内容,我们的用户必须与我们login,但这是分开处理的。
-
为了确定数据的显示位置,使用公共API密钥来限制对我们所知的域的访问,并且最重要的是确保私有用户数据不容易受到CSRF的攻击。
这个API密钥对任何人都是可见的,我们不以任何其他方式对我们的合作伙伴进行身份validation,我们不需要REFERER 。 不过,它是安全的:
-
当我们的
get-csrf-token.js?apiKey=abc123
被请求时:-
查找数据库中的密钥
abc123
,并获取该密钥的有效域列表。 -
寻找CSRFvalidationcookie。 如果它不存在,则生成一个安全的随机值,并将其放入仅HTTP会话cookie中。 如果cookie确实存在,则获取现有的随机值。
-
从API密钥创build一个CSRF令牌和cookie中的随机值,并对其进行签名 。 (而不是在服务器上保存一个令牌列表,我们正在对这些值进行签名,两个值在签名的令牌中都是可读的,没关系。)
-
将响应设置为不被caching,添加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">
(或一个dynamic的等价物)加载它,然后运行码。 当然,你的服务器不应该支持生成的JavaScript的跨源资源共享和JSONP。 -
在加载上述脚本之前,浏览器脚本可以更改
document.domain
的值。 但同样的原产地策略只允许通过删除前缀来缩短域名,如将subdomain.example.com
重写为example.com
,或将myblog.wordpress.com
为wordpress.com
,或者在某些浏览器甚至bbc.co.uk
co.uk
-
如果使用某些服务器端脚本获取JavaScript文件,则服务器也将获取该cookie。 但是,第三方服务器不能让用户的浏览器将该cookie关联到我们的域名。 因此,使用服务器端脚本获取的CSRF令牌和validationCookie只能被随后的服务器端调用使用,而不能在浏览器中使用。 但是,这样的服务器端调用永远不会包含用户cookie,因此只能获取公共数据。 服务器端脚本可以直接从合作伙伴的网站上获取相同的数据。
-
-
当用户login时,以任何你喜欢的方式设置一些用户的cookie。 (用户在请求JavaScript之前可能已经login了。)
-
对服务器的所有后续API请求(包括GET和JSONP请求)都必须包含CSRF令牌,CSRFvalidationcookie以及(如果已login)用户cookie。 服务器现在可以确定请求是否被信任:
-
存在有效的CSRF令牌可确保JavaScript从预期的域( 如果由浏览器加载)加载。
-
没有validationcookie的CSRF标记的存在表示伪造。
-
CSRF令牌和CSRFvalidationCookie的存在并不能确保任何内容:这可能是伪造的服务器端请求,也可能是来自浏览器的有效请求。 (这不能是来自不受支持的域的浏览器的请求。)
-
用户cookie的存在确保用户login,但不能确保用户是给定伙伴的成员,也不能确保用户正在查看正确的网站。
-
没有 CSRFvalidationcookie的用户cookie的存在表示伪造。
-
用户cookie的存在确保当前请求通过浏览器进行。 (假设用户不会在一个不知名的网站上input他们的凭证,并且假设我们不关心用户使用他们自己的凭证来提出一些服务器端请求)。如果我们也有CSRFvalidationcookie,那么CSRFvalidationcookie是也收到了使用浏览器。 接下来,如果我们也有一个拥有有效签名的CSRF令牌, 并且 CSRFvalidationcookie中的随机数与CSRF令牌中的随机数相匹配,那么在该CSRF的早期请求期间也接收到该令牌的JavaScript cookie被设置,因此也使用浏览器。 这也意味着上面的JavaScript代码在令牌被设置之前被执行,并且那个时候该域对于给定的API密钥是有效的。
所以:服务器现在可以安全地使用来自签名令牌的API密钥。
-
如果服务器在任何时候都不信任请求,则返回403 Forbidden。 小部件可以通过向用户显示警告来做出响应。
-
不需要签署CSRFvalidationcookie,因为我们将其与签名的CSRF令牌进行比较。 不签名cookie会使每个HTTP请求更短,并且服务器validation速度更快。
生成的CSRF令牌无限期地有效,但只有在与validationcookie一起使用时才有效,直到浏览器closures。
我们可以限制令牌签名的生命周期。 我们可以在用户注销时删除CSRFvalidationcookie,以符合OWASP的build议 。 为了不在多个合作伙伴之间共享每个用户的随机数,可以将API密钥添加到cookie名称。 但即使如此,当请求新的令牌时,即使这样,也不能轻易地刷新CSRFvalidationcookie,因为用户可能在多个窗口中浏览相同的站点,共享单个cookie(当刷新时将在所有窗口中更新,之后其他窗口中的JavaScript令牌将不再匹配该单个cookie)。
对于那些使用OAuth的人,另请参阅OAuth和客户端小部件 ,我从中获得了JavaScript的想法。 对于API的服务器端使用,我们不能依赖JavaScript代码来限制域,我们使用密钥而不是公共API密钥。
这个问题有一个可以接受的答案,但只是为了澄清,共享秘密authentication的作品是这样的:
- 客户端拥有公共密钥,可以与任何人共享,无所谓,因此您可以将其embedded到JavaScript中。 这用于识别服务器上的用户。
- 服务器有秘密密钥,这个秘密必须被保护。 因此,共享密钥身份validation要求您可以保护您的密钥。 所以直接连接到另一个服务的公共javascript客户端是不可能的,因为你需要一个服务器中间人来保护这个秘密。
- 服务器使用一些包含密钥的algorithm来签名请求(密钥有点像盐),最好是时间戳,然后将请求发送给服务。 时间戳是为了防止“重放”攻击。 请求的签名仅在约n秒内有效。 您可以通过获取应该包含签名中包含的时间戳值的时间戳头来检查服务器。 如果该时间戳已过期,则请求失败。
- 服务获取的请求不仅包含签名,还包含所有以纯文本签名的字段。
- 该服务然后使用共享密钥以相同的方式签署请求并比较签名。
我把你的意思是会话密钥而不是API密钥。 这个问题是从http协议inheritance的,被称为Session劫持 。 与任何网站一样,正常的“解决方法”是更改为https。
要安全地运行REST服务,您必须启用https,并且可能需要客户端身份validation。 但毕竟,这超出了REST的想法。 REST从不谈安全。
你想在服务器端做什么是生成一个过期的会话ID,在login或注册时发送回客户端。 客户端然后可以使用该会话ID作为共享密钥来签署后续请求。
会话ID只传递一次,这必须通过SSL。
看这里的例子
签署请求时使用随机数和时间戳以防止会话劫持。
我会试着回答这个问题的原始上下文。 所以问题是“秘密(API)密钥安全地放在JavaScript中。
在我看来,这是非常不安全的,因为它破坏了系统间authentication的目的。 由于密钥将暴露给用户,用户可以检索他/她没有被授权的信息。 因为在典型的rest通信中,身份validation仅基于API密钥。
我认为一个解决scheme是JavaScript调用本质上将请求传递给负责进行rest调用的内部服务器组件。 内部服务器组件让我们说一个Servlet将从一个安全的源(例如基于权限的文件系统)读取API密钥,插入到HTTP头部并进行外部rest调用。
我希望这有帮助。