23.3  接口的安全控制规范

23.2节的示例实现了一个简单接口,但是这个接口此时是在“裸奔”的。因为这个接口所有人都可以请求,不仅我们的客户端可以正常访问数据,如果有人使用如fiddler、wireshark等抓包工具,就很容易获取这个API地址,可以随意地请求获取或篡改我们的数据,这很显然是不安全的。因此,在设计接口时必须加上安全控制这一环节。

23.3.1  API安全控制原则

由于Web API是基于互联网的应用,因此对安全性的要求远比在本地访问数据库严格得多。一般通用的做法是,采用参数加密签名方式传递,即在传递参数时,增加一个加密签名,在服务器端验证签名内容,防止被篡改。对一般的接口访问,只需要使用用户身份的token进行校验,只有检查通过才允许访问数据。API常用的安全控制原则有以下几种:

(1)使用用户名密码。这种方式比较简单,可以有效识别用户的身份(如用户信息、密码,或者相关的接口权限等)。验证成功后,返回相关的数据。

(2)使用安全签名。这种方式提交的数据,URL的连接参数是要经过一定规则的安全加密的,服务器收到数据后也经过同样的规则进行安全加密,确认数据没有被中途篡改后,再进行数据的修改处理。因此,我们可以为不同客户端,如Web、App等不同接入方式指定不同的密钥,但是密钥是双方约定的,并不在网络连接上传输,连接传输的一般是接入的“key”,服务器通过这个“key”来进行签名参数的加密对比。目前,微信后台的回调处理机制,采用的就是这种方法。

(3)公开的接口调用,不需要传入用户令牌,或者对参数进行加密签名,这种接口一般较少,只是提供一些很常规的数据显示而已。

23.3.2  API安全控制简单实现步骤

API的安全控制方法有很多,可以根据项目自身的情况定制一些方法,也可以借鉴一些大的平台处理接口的算法。本节通过一些简单的控制方式,来一步步实现API的安全访问控制。

1.增加时间戳参数

首先,我们在API的URL中添加一个时间戳参数,如“timestamp”,要求请求的客户端在请求接口的时候必须添加此参数。如果在请求的时候没有该参数,就不返回数据。另外,通过时间戳参数,也可以限制请求接口必须要在某个时间段内完成,即便有人发现了接口地址,也只能使用一段时间。加入时间戳参数后,请求接口的URL地址格式如下所示:

https://localhost/userapi.php?id=1&timestamp=1519552181

修改上例中的userclient.php文件,分别使用get()方法请求两次userapi.php接口,一次在URL加上“timestamp”参数,一次不加该参数。修改后代码片段如下所示:

同样地,修改userapi.php接口文件,判断在请求接口时,请求的URL中是否带有“timestamp”参数,并且限制该URL只能在5分钟内有效。修改后的代码片段如下所示:

运行的结果如图23-8所示。

图23-8  测试请求API的URL参数带有时间戳的结果

虽然我们实现了客户端软件在请求API时需要添加“timestamp”参数才能获取数据,但这样依然不能防止别人获取我们的数据,因为通过抓包工具依然是可以看到地址的,所以别人也可以添加 “timestamp”参数请求我们的接口。限时访问也只能瞒得过一般的程序员,稍微细心的程序员就会发现这个规律,他可以生成当前的时间戳,然后模拟参数发送请求来获取数据。

2. 增加签名参数

在发送API调用请求时,为了确保客户端应用与API服务器之间的安全通信,防止盗用URL、数据篡改等恶意攻击行为,在API验证规则中可以使用参数签名机制。过程是客户端应用在调用API之前,需要通过算法计算一个加密的签名,并追加到请求参数中,参数名可以为“sign”。API服务器在接收到请求时,使用同样的算法重新计算签名,并判断其值是否与应用传递来的“sign”参数值一致,以此判定当前API调用请求是否是被第三方伪造或篡改的。

签名的算法很多,本节模拟支付宝的签名算法。例如,制定一个规则,将所有URL的参数提取出来,然后根据参数名进行排序,再将排序后的数组拼接成字符串,最后对该字符串进行 md5或sha1加密(建议使用sha1)后得到“sign”。例如,当前我们的URL 如下所示:

http://localhost/userapi.php?id=1&timestamp=1527068730

(1)得到参数数组:['timestamp'=> 1527068730, 'id'=>100]。

(2)键名根据 ASCII 码进行排序后:['id'=>100,  'timestamp'=> 1527068730]。

(3)组合成字符串:id=100timestamp=1527068730。

(4)使用sha1()函数加密得到fd8cc3348652b9cbf2714689ab7ee9105da67cf4。

客户端和API服务器端签名的计算方法相同,计算后的请求URL地址如下所示:

http://localhost/userapi.php?id=1&timestamp=1527068730&sign=fd8cc3348652b9cbf2714689ab7ee9105da67cf4

继续修改上例中的userclient.php文件,再分别使用get()方法请求3次userapi.php接口,第一次没有添加“sign”参数,第二次使用错误的“sign”参数,第三次使用全部正确的参数。并通过上面的算法生成“sign”参数。修改后代码片段如下所示:

同样地,修改userapi.php接口文件,判断在请求接口时,请求的URL是否带有“sign”参数,和客户端使用相同的算法计算签名,并和URL中接收到的客户端“sign”参数进行匹配,如果相同则返回数据,如果不同则可能被篡改,返回错误消息。修改后代码片段如下所示:

运行的结果如图23-9所示:

图23-9  测试请求API的URL参数带有sign的结果

通过签名参数,能大大提高接口的安全性,其他人不能随意地请求接口。虽然有人也可以抓取接口地址,但是也只能获取这一条数据,不能请求别的数据。例如,有人抓取了上例中的这个接口地址,它只能获取ID为1的文章。并且5分钟的时间内,它无法通过修改参数来获取ID为100的文章,因为一旦参数变化,“sign”参数校验就会失败。支付宝接口就是这样做的,避免交易金额随便被更改。

3. 引入token

虽然通过添加“时间戳”和“签名”参数,接口相对比较安全了,但是还是有隐患,如果别人知道了加密规则(当然规则可以变化,如倒序排序,或双层sha1加密等),采用相同的规则加密参数得到“sign”,就能接着获取其他的数据了。所以,需要再引入另一个元素“token”,它是一个约定值,相当于“暗号”,其实就是只有客户端和服务器端知道的一个随机字符串。当然这个“token”字符串在客户端用户那里是不可见的,如果客户端是App,代码是编译过的,客户端使用PHP程序,在服务器上不会传给用户。引入“token”只需要将这个随机的字符串加入sign 的计算中,就能再次提高接口的安全性。 继续修改上例中的userclient.php文件,引入“token”进入签名运算。修改后代码片段如下所示:

继续修改上例中的userapi.php文件,同样引入相同内容的“token”进入签名运算中。修改后代码片段如下所示:

再次运行后,和上例运行得到的结果相同。到此为止,我们的接口已经变得比较的安全了,其他人想要请求我们的接口,就必须知道我们的签名加密规则和随机的“token”字符串,这个可能性就太低了。

最新文章

  1. Json CPP 中文支持与入门示例
  2. IPC操作时IPC_CREAT和IPC_EXCL选项的说明
  3. *HDU 1086 计算几何
  4. Android原生(Native)C开发之一:环境搭建篇
  5. Spring Annotation Processing: How It Works--转
  6. Spring注解@Value的用法
  7. 数据连接到 Web 服务 InfoPath 2010 窗体中的 SharePoint 服务器上运行时的错误消息:"401-未经授权"解决方案
  8. Linux IPC POSIX 共享内存
  9. 6.6 Android 编译机制的变迁
  10. MyEclipse 2015优化技巧
  11. node.js回调函数 - 阻塞与非阻塞
  12. Java Web应用启动间隔执行的程序
  13. What exactly is the difference between WndProc and DefaultWndProc?
  14. PHP MySQL 插入多条数据
  15. 调用Response.Redirect 捕获异常 解决办法(摘抄)
  16. IOS研究院之打开照相机与本地相册选择图片
  17. 【经典dp】 poj 3671
  18. linux 中环境变量配置错误导致部分命令不能使用包括vi
  19. spring mvc 配置之 context:annotation-config vs component-scan
  20. 基于嵌入式linux路由转发功能的实现

热门文章

  1. 字典 dict方法
  2. 12. java ArrayList类
  3. 十ITK读取一张dcm图像然后通过vtk显示
  4. RTP中的H264的SVC相关信息
  5. Vue小练习 03
  6. Linux中fuser命令用法详解
  7. error: (-215:Assertion failed) !_src.empty() in function 'cv::cvtColor'
  8. Django 资源 与 知识 Django中自建脚本并使用Django环境 model中的save()方法说明 filter()用法
  9. PHP获取二维数组指定字段值的和
  10. RAC_多路径配置