`
bugtags
  • 浏览: 27524 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

RESTful Api 身份认证中的安全性设计探讨

 
阅读更多

REST 是一种软件架构风格。RESTful Api 是基于 HTTP 协议的 Api,是无状态传输。它的核心是将所有的 Api 都理解为一个网络资源。将所有的客户端和服务器的状态转移(动作)封装到 HTTP 请求的 Method  之中.

而本篇文章则主要是讨论 RESTful Api 身份认证安全性设计.

没有绝对的安全,这个话题很深,下文都是自己的一些理解,水平有限,如有勘误,希望大家予以指正。

由于 RESTful Api 是基于 Http 协议的 Api,是无状态传输,所以只要和用户身份有关的请求都会带上身份认证信息。(很多时候客户端事先并不知道某个 api 后期会不会加入身份判断,所以我们一般都会选择每个请求都会带上认证信息,如果有的话。

Http Basic Authentication

Http Basic 是一种比较简单的身份认证方式。在 Http header 中添加键值对 Authorization:  Basic xxx (xxx 是 username:passowrd base64 值)。

例如 username 为 zmk ,password 为 123456,请求则如下

GET /auth/basic/ HTTP/1.1

Host: xxxxx

Authorization: Basic em1rOjEyMzQ1Ng==

而 Base64 的解码是非常方便的,如果不使用 Https ,相当于是帐号密码直接暴露在请求中。

危险性高,实际开发者使用的应该几乎为 0。

顺便提下 DIGEST 认证,和 BASIC 认证相差无几,而且不适合 api 设计,实际又需要两次请求,首次请求,服务器端返回 401,并且带上 nonce 值,然后客户端再利用 username+password+nonce 默认 MD5 之后再请求。对 http 请求的作用是仅仅防止二次请求,对身份认证并没有什么提升。

Access Token

不知道是否应该这么称呼。原理即当客户端登录完毕之后,给客户端返回一个 token,服务器端控制该 token 的有效期,每次请求都带上该值,然后服务器端做验证,退出之后,客户端通知服务端端销毁 token,客户端本地也销毁。但是如果抓包获取到 token,就能任意伪造请求了。

同时 api 接口还存在被第三方开发者或者公司随意利用的风险。也就是说,别人可以非常轻易的就弄出一个你们 app 的复制版,而且还用的你们的所有资源。

危险性高,实际开发估计使用得还不少。

Api Key + Security Key + Sign

下图是我们自己每次请求的身份认证的方式,如有不足,请大家指出。可以说是 JWT 的自定义版吧。


 

这里的认证逻辑即:

用户登录返回一个 api_key 和 security_key;

然后客户端将 security_key 存在客户端;

当要发送请求之前,通过 function2 加密方法,把如图所示的五个值一起加密,得到一个 sign;

发送请求的时候,则将除去 security_key 之外的值,以及 sign 一起发送给服务器端;

服务器端首先验证时间戳是否有效,比如是服务器时间戳 5 分钟之前的请求视为无效;

然后根据 api_key 得到 sercurity_key;

最后验证 sign。

Api key 的作用是什么?(补)

看到有朋友在头条问了这个问题,说下我的实际使用场景:

api key 是用来标识每个不同用户的(也就是说 api key 和用户 id 一一对应的),同时也用来验证security_key 和 sign 的。

比如有 2000 万用户,以 redis 作为数据库,将 api_key 为键,security_key 作为值,api_key 散列分布(比如对末尾位字符的 ASCII 对 20 取模)到 20 个 hashes 里。

当用户请求过来的时候首先根据 api_key 找到对应的 hashes,首先 HEXISTS 检查该 api_key 是否存在,存在则通过 HGET 取出该值,最后一起验证 sign。

是否需要加上时间戳验证?

上面的认证逻辑中加密得到签名的时候,把时间戳加进去是为了在一定程度上屏蔽了一些无效的请求,可以略去,也可以设计的更加严格。如果想防止恶意的 api ddos 攻击,这一步验证肯定是不行的。需要做更多的验证,比如用户验证,ip 验证等。可以参考 github 的 api 的设计。它会在返回的 http 头信息里带上

X-RateLimit-Limit: 5000

X-RateLimit-Remaining: 4999

表示这个接口在某一时间段内,该授权用户调用该接口的最大次数为 5000次,该时间段内还剩余 4999 次。当然,这样的验证加上之后,在代码的执行效率上肯定会有所影响。

是否需要将 request_parameters 也加入到 sign 生成的算法之中?

也不是必须的,仅仅是为了请求的真实性,减少请求的伪造,比如有人抓包拿到 http 请求之后,如果没有验证 sign 这步,那么别人就可以非常简单的修改请求的参数,而请求都会生效。

这里将 request_parameters 也加入到签名之中,就减少了伪造请求的可能性,但是无法杜绝,破坏者可能就非要黑你,又对逆向工程非常熟悉,找到我们加密算法的实现,依然可以未知出合法的签名,所以我们常说,服务器端永远不能相信客户端的请求都是安全的、合法的,需要做验证的都还是不能省略。

同时这(sign算法)也造成了 api 接口调试的成本,api 测试工具必须也得实现那一套算法,或者是设置在开发环境下不做验证。我们在配置开发环境的时候则是 vpn 连测试服务器所在内网,然后进行测试,否则开发环境也存在被人利用的风险。

项目实例 https://github.com/zhoumengkang/netty-restful-server

JWT

JWT (JSON Web Token) 使用流程如下(图片来自官网)


 

其认证机制也是登录,发放密钥给客户端,然后客户端每次发送请求的时候通过 JWT 的算法规则组装 JWT 的Auth Header,服务器端作验证。

web 授权认证的原理万变不离其宗,都是如此。

只不过 JWT 呢,自定了一套认证协议。格式为 Header.Payload.Signature。比如 xxxxx.yyyyy.zzzzz。签名内容是有 Header+Payload+Secret 通过 HMAC SHA256 算法加密而成。

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

secret)

而请求的很多参数键值对都可以放在 Payload 里面。完整讲解请求看官方的介绍 http://jwt.io/introduction/

需要注意的一点,依照 JWT 的协议,只有一个 secret,无法得知该用户是谁,所以在 secret 该值中必须要可以解码出用户的 id。

而我们自定义认证协议的时候 header 感觉就没有必要了,使用什么算法事先定义好即可。所以我们也没选择这种方式而是上面的那种方式。

其他

oauth2.0 则属于第三方认证,不在本篇的讨论范畴之内,可以阅读 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

来自:周梦康

链接: http://mengkang.net/620.html

 


 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics