译者:阿诺斯基
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
API驱动的世界
毫无疑问,随着时间的推移,我们所生活的世界正在变得越来越紧密。新的服务正在出现,目的是使我们的生活更简单,更愉快。在所有这些变化的背后,这些服务的组合是一种真正进入自己的技术:API驱动架构。这些API允许服务在程序级别相互通信,并提供其他服务可以以自己有用的方式消费和使用的可预测的响应功能。
如果你在过去七,八年内一直在进行着任何形式的开发,那么你可能会遇到某种形式或前卫的API。也许你为你的公司创建了一个API,或者你是一个创业公司,而API 就是你的产品。这几天有个笑话是,如果你没有API,那么你的服务久已经落后于时代。
然而,伴随着API的创建也会产生一些犹豫。开发人员在前端,面向客户的开发和功能方面更加舒适,在深入了解API的过程中,可能会感觉到其构建的深度。虽然基础的知识仍然存在(如HTTP请求/响应和输入处理),但它们的实现方式存在不同的问题。表示层被完全删除,重点放在被共享的数据上,而不是它的外观。你的任务是制定一些功能和实用性,使其具有更好的演示性和用户体验。
这里有一个简短的说明 – 本文将重点介绍REST API,而不是SOAP(或RPC)。REST在API领域已经明显地领先,如果你计划实施新的API,那这肯定是你选择的方向。
如果一个开发人员对于API的世界来说是相对陌生的,那么很自然的倾向就是做出“只是工作”的东西 – 在特定的端点上接收数据请求,并回复所请求的匹配。虽然这是最终的目标,但是在创建API时也有个事情同样重要:做出选择并做好充分保护的工作。这很容易理解,因为它是一个API,只有其他的“机器”会说话,它的安全性可以比面向客户的应用程序更加宽松。显然,没有什么可以从真相中更进一步。但是,在确保面向客户的工具中不需要的API时,就需要考虑到这些问题。让我们先来看一下这些概念和考虑因素,
概念
首先,让我们来谈谈有关保护API的一些基础知识。要开始这个话题,我会在这里先说一件事 – 稍后将介绍保护API的方式并不是唯一的办法。这只是一个方法,我已经放在一起说明了一些API的基本安全概念。有一个全球性的API保护方案,他们应该在你确定之前进行评估。我将展示的方法对于一个希望安全的API来说非常棒,但是不希望其他系统可以带来很多开销。
我们将使用的方法是使用“共享密钥”系统,其中API和客户端都具有用于认证和发送消息的秘密信息(在这种情况下为令牌)。我看到一些类似系统的系统实现,并利用一个系统,每个请求使用一个静态标记。但是,如果攻击者发现了这个令牌,这可能会导致严重的问题。攻击者可以模拟用户的消息并潜在地绕过验证机制。
我这里说的不是一个单一的令牌系统,我已经选择了一个多重令牌,可撤销的系统,让用户创建他们想要的令牌,描述他们的使用,另一个主要的好处 – 随时撤销它们。如果你是GitHub用户,那么你可能熟悉它的令牌系统。它以类似的方式工作,只需要添加更多元数据。
如果你熟悉OAuth v2的处理过程,那么这个设置听起来久很熟悉。与OAuth事务有一个类似的秘密令牌和前后切换,但这里会涉及到更多的东西。另外请记住,设计OAuth并不是主要用于身份验证。其主要目的是授权。例如,考虑在网站上看到的所有“使用Twitter登录”功能。当你点击该按钮时,应用程序会将你返回到Twitter站点并要求获得授权批准。你点击“允许”按钮,流程就会继续。这就是OAuth,它正在处理幕后的事情。使用该流程,你就已经授权应用程序使用你的Twitter信息来识别你。然而,到Twitter可以登录你的帐户时,另一个应用程序并不知道你是否真的是你所说的 – 他们已经接受了风险,并将这一决定移交给了Twitter。
在这里有一个这样的警告:在使用像这样的基于令牌的系统时,请确保所请求的URL中不是以令牌结束的。默认情况下,URL请求会记录到Web服务器日志中。如果你的URL中的令牌(特别是生命周期比较长的)被攻击者获取,所有的攻击者必须做的是找到你的日志存储的位置,并访问他们想要的所有令牌。
在我们将要创建的系统中,还将有另一个方面的令牌来帮助提高他们的安全性 – 限制他们的有效时间。我在这里谈论的令牌是认证后的请求中所使用的令牌,而不是用于身份验证的令牌。通过限制这些令牌“存活”的时间,我们能够减少攻击者被拦截和重新使用的可能性。在我们的系统中,会将token的有效时间限制为1小时。这个时间内通常足以让用户能够使用API成功运行,但仍然需要为请求提供足够的保护。
当令牌过期时,客户端将被发送一个关于认证失败的消息,他们只需要请求一个新的令牌。在OAuth的世界里,他们发送一个“刷新令牌”,它专门用于这种令牌“refetch”请求,但是由于我们在这里搞了一个更简单,更轻的版本,所以我们只需要坚持手动要求获取新的令牌就行了。
基本流程
我已经描述了这个API的运作过程,但是让我花一些时间来遍历正常会话的流程,包括身份验证和剩余的请求。
1.令牌创建
我们将会把用户进入应用程序的管理部分当做次过程的开始,并生成用于身份验证过程的新令牌。该令牌是一个随机的字符串,数字和符号,在认证请求期间(并且仅用于)使用。
2.认证
一旦我们拥有了令牌,用户将向API上的端点发出一个POST请求及其凭据:用户名和“密码”的令牌。如前面所述,你不希望将此请求使用GET方式,因为这会将凭据信息显示在服务器日志中…这是一件不好的事情。
对令牌和所提供的用户名进行查找,并确保匹配。如果一切都很好,那么响应将包含我们随机生成的有时间限制的令牌,用于当前会话。该令牌用作以下请求中的标识符,并确保正在发送的消息未被篡改。如果用户在该令牌仍然有效的时间内执行另一个认证请求,则每次都会给予新的令牌,以进一步降低潜在攻击者的令牌拦截和重用的风险。
3.跟随请求
有了这个令牌,客户端就可以向API中的其余端点发出请求。我们的系统目前不会强制执行任何权限,只有已经认证的网关。在一个真正的API中,需要有一个更复杂的系统来帮助保护各个端点,并确保只有使用它们的用户才能访问。
要发出请求,客户端必须执行几个任务:
1.在名为“X-Token”的header中发送认证产生的令牌
2.创建要发送的消息的内容的HMAC散列,并将其包括为X-Token-Hash头
使用PHP,第二步可以很容易的使用hash_hmac方法实现:
<?php
$body = json_encode(['foo' => 'bar']);
$messageHash = hash_hmac('SHA512', $body, $hash.time());
?>
在上面的代码中,我们正在创建$body内容的SHA512 HMAC散列,并使用$hash身份验证请求以及当前时间(以秒为单位)作为key。当消息回到服务器时,这个哈希值是根据相同的信息重新创建的,并且与X-Token-Hash header中发送的值进行比较。如果系统发现不匹配,那么系统就知道消息的内容已被更改,并可以完全拒绝。
精明的读者可能已经注意到这个请求处理的一个小问题 –需要在几秒钟内使用当前时间。在过去一段时间,我已经在工作中用到了很多API,这的确成为了一个问题,因为他们的用户遍布世界各地,有时候他们的系统上的时钟没有正确的同步。因此,无论发送什么,请求均会出现“Bad Request”的消息。为了帮助解决这种情况,你可以将被使用的时间值修改的“稍长一点”,使用分钟级的时间而不是秒级的时间。包含时间的要点是提供一些随机性来生成HMAC哈希值,因此每个请求都不一样。如果把它设置为秒级别的时间会造成很多问题,
<?php
$body = json_encode(['foo' => 'bar']);
$time = date('ymdHi');
$messageHash = hash_hmac('SHA512', $body, $hash.$time);
?>
你基本上只需要在请求的+59秒内添加。这不太安全,但如果这意味着更多的用户请求正在通过,那么这是一个相对公平的权衡。只要记住将其标记为你身边的被接受的风险,这样就不会被遗忘。
工具
虽然这里提出的概念可以适用于你可以选择的任何语言和任何框架,但是我必须选择一个作为开始。由于我将重点关注在了本系列中的PHP示例,所以我决定开发一个简单的PHP框架,我熟知的框架是Slim框架。这个框架在技术上是一个“微框架”,旨在为开发人员提供最少的功能,用于制作Web应用程序。在其基础上,它只是一个简单的前端控制器,并附加路由器和请求/响应处理。它没有太多的其他东西。有几个优点捆绑在了一起,但如果你正在寻找一个框架,你可以去寻找一个能为你提供一切的一个框架, Slim并不是最好的选择。
然而,这种简约的方法使其非常适合我们的例子,特别是当API更多的只是请求和响应周期比具有前端的Web应用程序。Slim提供了我们需要设置的一些基本路线并将其链接到我们的API的功能部件的一切。
只要给出一个我们谈论的内容的简单想法,一旦通过Composer安装,响应所有这些都需要的索引页面请求的应用程序是:
require_once 'vendor/autoload.php';
$app = new SlimApp();
$app->get('/', function() {
echo 'Hello world!';
});
$app->run();
这就是所有代码…在这个简化的结构中,我们将构建我们的API并集成其他几个部分,以帮助保护它及其要保护的数据。谈到其他部分,让我们来看看下一个在我们的请求/响应周期中给我们提供一些可重用的逻辑:中间件。
如果你不熟悉中间件的概念,其实这是一个非常简单的概念。我一般是一个视觉学习者,所以我发现这个图(从Slim框架借来的)很有用:
中间件解释
如图所示,中间件的基本思想是围绕你的应用程序的主要部分进行“包装”。它旨在提供以请求和响应处理为中心的附加功能。当然,它也可以做其他的事情,但是大多数中间件在整个HTTP请求中处理数据流方面表现出色。该请求进入应用程序,通过中间件层,一旦内部处理完成,它将以相反的顺序返回到相同的中间件层。
这个中间件层是我们在示例应用程序中执行某些授权逻辑的地方。由于需要在每个请求上检查API访问级别,所以将其包装在中间件中是很有意义的,并检查所需数据的传入请求。这种方法还允许我们在控制器和内部的逻辑出现授权问题时,踢掉客户端。
接下来是我们将在API示例中使用数据库的两个软件包:Laravel Enloquent数据库层和Phinx迁移工具。如果你是PHP开发人员,那么你可能会听说过Laravel框架。这个框架在过去几年中人气大涨,由于其易用性和“简单”的感觉,已经获得了很多人的追捧。虽然框架本身具有很多功能,但我们这里的这些示例中Eloquent包仅用于处理数据库。
幸运的是,这个包可以在主要的Laravel框架之外使用,因为“胶囊”这种特性。有了这个,我们可以将Eloquent及其功能引入我们基于Slim的应用程序,并像Laravel应用程序一样使用它。你可以查看“Eloquent”手册了解更多信息,以下是使用它的示例:
$links = Link::all();
$users = User::where(['active' => 1])->get();
$userLinks = User::find(1)->links;
它不仅允许直接提取记录,还可以搜索数据库信息并在模型之间建立关系,从而使你更容易在PHP中交叉引用数据,而不会对SQL造成困扰。
Phinx工具使我们能够进行可重用的数据库迁移。迁移基本上是一种自动执行SQL命令的方法。当你考虑开发人员面临的一些常见的数据库问题时,迁移的真正好处就非常明显了:初始数据库设置的一致性,并保持整个团队的同步性。Phinx是一个基于PHP的工具,可以让你创建迁移,也可以用PHP编写,让你定义迁移的“up”和“down”逻辑:当新增数据时执行“up“,当需要移除数据时执行“down“。Phinx工具还可以跟踪哪些迁移已经运行,如果更改后有意想不到的后果,则可以回滚。
以下是创建表的Phinx迁移的示例:
<?php
use PhinxMigrationAbstractMigration;
class CreateSources extends AbstractMigration
{
public function change()
{
$table = $this->table('sources');
$table->addColumn('name', 'string')
->addColumn('type', 'string')
->addColumn('user_id', 'string')
->addColumn('source', 'string')
->addColumn('last_update', 'datetime', ['default' => 'CURRENT_TIMESTAMP'])
->addColumn('created_at', 'datetime')
->addcolumn('updated_at', 'datetime')
->create();
}
}
在这种情况下,我们创建一个sources表,其中包含用户引用的列,类型,名称和源值。你会注意到,在这个例子中没有一个具体的up或者down方法。随着更新版本的Phinx,该工具已经使用一些魔法实现了change方法。当你写一个change方法时,Phinx会尽力找出,并根据正在执行的操作(应用或回滚)判断如何处理迁移。在这个例子的情况下,它比较简单:执行up,表将被创建,并且执行down,表将被删除。
还有一些其他随机的功能将被包括在随机数生成函数和自定义异常处理的方式,但不要担心,这些都将被及时覆盖。
下集预告
这是本系列文章的第一部分的结尾。我向你描述了API生态系统的当前状态,描述了应用程序的基本流程,并列出了我们将在本系列中使用的一些工具来实现魔术方法。在本系列的下一部分中,我们将花费一些时间通过规划我们的一些基本API功能来设置和通信。
参考资源