REST API的身份验证(Authentication)

George Reese在文中概括了他认为REST API Authentication所应该遵循的3条原则: 

1. All REST API calls must take place over HTTPS with a certificate signed by a trusted CA. All clients must validate the certificate before interacting with the server. 

译:所有REST API请求都必须通过加密的HTTPS协议来传递,加密所用的证书应由一个trusted CA (certificate authority)签发。所有客户端必须验证服务器端的身份证书,然后才开始进行会话。 

关 于这点我想应该是毫无疑义的。所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床。所谓man-in-the-middle攻击简单讲就是指恶意的黑客可以在客户端和服务器端的明文通信通道上做手 脚,黑客可以监听通信内容,偷取机密信息,甚至可以篡改通信内容,而通过注入RSA公匙加密后的通信内容理论上是无法被破译的(除非对应的private key被偷了…)。George很幽默地说道:如果你没有对你的API调用请求加密,你甚至没有在假装很安全。 

不 过另外有一点经常被忽略的就是:即使使用了加密的HTTPS协议,客户端也必须对服务器端的身份进行验证。每当说到身份验证 (Authentication),人们总是先想到服务器端对客户端进行的身份验证(以确定你是哪个用户),但其实反向的客户端对服务器端的验证也是极为 重要的。在这个飞贼横行,假冒遍地的年代,你怎么知道为你提供服务的对象真的是你所指定的服务器呢?一个简单的spoof,或者被恶意篡改过的hosts 文件,都可能让你在不知不觉中把各种机密信息心甘情愿地交给躲在暗处的偷儿。但请注意我这里并不是说我们用的authentication模式一定是传统 意义上基于电子证书的Mutual SSL Authenatication,因为客户端不一定会拥有或使用独立电子证书,事实了,基于经济成本的考虑,大多数end-user并没有由一个 trusted CA发布的电子证书(这玩意儿可不是太便宜,至少大多数情况下是这样,嗯,当然有些例外,比如GoDaddy这种的,记得那时10美元就可以搞一个, 汗…) 


2. All REST API calls should occur through dedicated API keys consisting of an identifying component and a shared, private secret. Systems must allow a given customer to have multiple active API keys and de-activate individual keys easily. 

译:所有 REST API必须由专门的API密匙来验证。API密匙(注意:这里的密匙是广义的,不一定是指public key authentication中的key)必须有一个身份确认段,以及一个客户端/服务器端共享的私有秘密。服务器端的系统必须允许每个用户拥有一个或多 个API密匙,并且可以方便地禁用某些密匙(使之不再有效)。 

这段话可能是文中写的最模糊的内容,我还是结合我们当年设计的方案来解释一下吧: 

我 们当年提供的客户端验证手段主要是基于mutual ssl(如果客户拥有自己的电子证书的话)和我们自己设计的一种authentication token。客户可以随时向我们申请任意个authentication token,我们会通过一个HTTPS通道将authentication token发送给客户,客户拿到token以后应该妥善保存,不应该和任何人分享(因为这是他们的身份证明,如果别人知道了,就可以冒充他们了)。之后, 在客户向我们递交请求时(同样还是在一个HTTPS通道上),他必须同时递交一个authentication token来证明他的身份,我们在服务器端会进行验证。 

Authentication token的设计大致是这样的: 

AuthenticationToken = RealmString “:” TimeStamp “:”Domain “:”SignedPackage 

而 其中最关键的SignedPackage是这样定义的:Base64(S(Gpri, RealmString + “:” + TimeStamp + “:” + DNS + OwnerPUID)),其中,Gpri是我们的private key,S代表签名操作,而Base64就是Base64编码。也就是说,SignedPackage是这样生成的:先取得这个字符 串:RealmString + “:” + TimeStamp + “:” + DNS + OwnerPUID,然后用我们的private key对该字符串进行签名加密,最后用Base64编码一下(这个只是为了HTTP协议传输的需要,和安全性无关)。 

对照George Reese的文章,就可以看出,他所谓的identifying component在我们的设计中就是DNS部分,而shared, private secret就是整个SignedPackage部分。请注意AuthenticationToken中的TimeStamp部分,也就是说,客户在不同 时刻申请的token都会是不同的,所以这使得用户可以拥有多个token。而我们在服务器端也用很简单的机制可以revoke任何已经发出的 token。 

最后可能有人要问这里的OwnerPUID是干什么的,这个其实是我们服务中的一个特殊的设计,和我们所提供的服务细节有关,这里就先不讨论了,也许下次有机会。 


3. All REST queries must be authenticated by signing the query parameters sorted in lower-case, alphabetical order using the private credential as the signing token. Signing should occur before URL encoding the query string. 

所有REST的查询请求都必须经过验证,验证的方式是客户端需对所有查询参数以小写字母顺序排序后用上文所提到的shared, private secret进行签名加密。签名加密的步骤应该在对查询字符串进行URL编码之前执行。 

Ok, 这点是我不敢苟同的。对于查询字串加密会造成很多问题:比如所有的代理服务器的功能都无效了,比如服务器端缓存就无法实现了,最搞笑的是用户如果想把某个REST查询在Facebook上分享给朋友都不可能了。 

其 实最最重要的是我觉得要求在REST查询的URL上夹带authentication信息是对REST原则的一种违反。我不敢说我完全理解Roy Fielding大师论文的真义,但我觉得REST协议的一个基本原则就是低耦合,也就是专项专用。URL就是用来决定resource的位置的,而不应 该也不必要再夹带其他功能,比如身份验证。身份验证的信息完全可以通过其他标准的HTTP协议的组件来实现(比如最简单的Authorization请求 报头 – request header)。 

你可能感兴趣的:(Authentication)