这是我们关于将NGINX Open Source和NGINX Plus部署为API网关的系列博客文章中的第2篇。
- 第1部分提供了多个用例的详细配置说明。
- 本文最初发布于2018年,现已更新,以反映当前的API配置实践,使用嵌套的
location块路由请求,而非使用重写规则。
本文在这些用例的基础上进一步展开,并介绍了可用于保护生产环境中后端API服务并增强其安全性的一系列防护措施:
- 第 3 部分介绍如何将NGINX Open Source和NGINX Plus部署为gRPC服务的API网关。
注意:除非另有说明,本文中的所有信息均适用于NGINX Open Source和NGINX Plus。为便于阅读,本文其余部分将二者统称为“NGINX”。
速率限制
与基于浏览器的客户端不同,单个API客户端可能会给API带来巨大负载,甚至占用大量系统资源,导致其他API客户端实际上无法访问。造成这种威胁的不仅是恶意客户端:行为异常或存在缺陷的API客户端也可能陷入循环,致使后端不堪重负。为防范此类情况,我们会实施速率限制,以确保各客户端公平使用API,并保护后端服务资源。
NGINX可以根据请求的任意属性实施速率限制。通常使用客户端IP地址,但为API启用身份验证后,经过身份验证的客户端ID是更可靠、更准确的属性。
速率限制本身在顶层API网关配置文件中定义,随后可应用于全局、各个API,甚至各个URI。
正在加载代码示例…
在此示例中,第4行的limit_req_zone指令为每个客户端IP地址($binary_remote_addr)定义了每秒10个请求的速率限制,第5行的该指令则为每个经过身份验证的客户端ID($http_apikey)定义了每秒200个请求的速率限制。这说明可以定义多个速率限制,而不受其应用位置的影响。一个API可以同时应用多个速率限制,也可以对不同资源应用不同的速率限制。
然后,在以下配置片段中,我们使用limit_req指令,在第 1 部分<.htmla>所述“Warehouse API”的策略部分应用第一个速率限制。默认情况下,超过速率限制时,NGINX 会发送503 (Service Unavailable)响应。不过,明确告知 API 客户端已超过速率限制,有助于客户端相应调整行为。为此,我们使用limit_req_status指令,改为发送429 (Too Many Requests)响应。
正在加载代码示例…
可以为limit_req指令添加其他参数,以微调NGINX实施速率限制的方式。例如,超过限制时,可以将请求加入队列,而不是直接拒绝,从而留出时间让请求速率降至设定的限制以下。有关微调速率限制的更多信息,请参阅博客文章《使用NGINX和NGINX Plus进行速率限制》。
强制使用特定请求方法
对于RESTful API,HTTP方法(或动词)是每次API调用的重要组成部分,对API定义也至关重要。以Warehouse API的定价服务为例:
GET/api/warehouse/pricing/item001返回item001的价格PATCH/api/warehouse/pricing/item001更改item001的价格
我们可以更新Warehouse API中的URI路由定义,使定价服务仅接受使用这两种HTTP方法的请求(库存服务仅接受使用GET方法的请求)。
正在加载代码示例…
采用此配置后,如果向定价服务发送请求时使用的不是第22行列出的方法(或向库存服务发送请求时使用的不是第13行所列的唯一方法),请求将被拒绝,不会传递到后端服务。NGINX会发送405 (方法 不 允许)响应,向API客户端说明错误的确切性质,如以下控制台跟踪所示。如果安全策略要求尽量减少信息披露,可以使用error_page指令将此响应改为信息量较少的错误,例如400 (错误 请求)。
$ curl https://api.example.com/api/warehouse/pricing/item001{"sku":"item001","price":179.99}
$ curl -X DELETE https://api.example.com/api/warehouse/pricing/item001
{"status":405,"message":"Method not allowed"}应用细粒度访问控制
本系列第 1 部分介绍了如何启用以下身份验证选项,保护 API 免遭未经授权的访问,例如API 密钥和JSON Web 令牌(JWT)。我们可以使用经过身份验证的 ID 或该 ID 的属性实施精细访问控制。
下面介绍两个此类示例。
- The 第一个示例控制对特定API资源的访问。该示例扩展了第1部分介绍的配置,并使用API密钥身份验证来确认指定的API客户端是否在允许列表中。
- The 第二个示例控制客户端可以使用哪些HTTP方法。该示例实现了第1部分中提到的JWT身份验证方法,并使用自定义声明来识别符合条件的API客户端。(请注意,只有NGINX Plus支持JWT。)
当然,这些示例用例也适用于其他身份验证方法,例如HTTP基本身份验证和OAuth 2.0令牌内省。
控制对特定资源的访问
假设我们只允许“基础设施客户端”访问Warehouse API库存服务的audit资源。启用API密钥身份验证后,我们使用map块创建基础设施客户端名称允许列表,以便在使用相应的API密钥时,变量$is_infrastructure的计算结果为1。
正在加载代码示例…
在Warehouse API的定义中,我们为库存location资源添加了一个audit块(第15至20行)。if块可确保只有基础设施客户端能够访问该资源。
正在加载代码示例…
请注意,第15行的location指令使用=(等号)修饰符,以精确匹配audit资源。精确匹配的优先级高于其他资源所使用的默认路径前缀定义。以下跟踪结果显示,采用此配置后,不在允许列表中的客户端无法访问库存audit资源。所示API密钥属于client_two(定义见第1部分)。
$ curl -H "apikey: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit{"status":403,"message":"Forbidden"}控制对特定方法的访问
如上文所述,定价服务接受GET和PATCH方法,客户端可分别使用这两种方法获取和修改特定商品的价格。(也可以选择允许POST和DELETE方法,以便对定价数据进行全生命周期管理。)本节将扩展该用例,以控制特定用户可以发出哪些方法请求。为Warehouse API启用JWT身份验证后,每个客户端的权限都会编码为自定义声明。向有权更改定价数据的管理员签发的JWT包含声明"admin":true。现在,我们将扩展访问控制逻辑,使只有管理员才能进行更改。
正在加载代码示例…
添加到map 文件底部的此代码块api_gateway.conf以请求方法($request_method)作为输入,并生成一个新变量 $admin_permitted_method。只读方法始终允许使用(第 62 至 64 行),但能否访问写入操作取决于 JWT 中 admin 声明的值(第 65 行)。现在,我们扩展 Warehouse API 配置,确保只有管理员才能更改定价。
正在加载代码示例…
Warehouse API要求所有客户端提供有效的JWT(第7行)。此外,还会通过评估$admin_permitted_method变量(第25行)来检查是否允许执行写入操作。再次提醒,JWT身份验证仅适用于NGINX Plus。
控制请求大小
HTTP API通常使用请求正文来承载供后端API服务处理的指令和数据。XML/SOAP API和JSON/REST API都是如此。因此,请求正文可能成为攻击后端API服务的途径;后端API服务在处理非常大的请求正文时,可能容易遭受缓冲区溢出攻击。
默认情况下,NGINX会拒绝请求正文大于1 MB的请求。对于图像处理等专门处理大型有效负载的API,可以提高此限制,但对于大多数API,我们会将其设置为较低的值。
正在加载代码示例…
第7行的“client_max_body_size”指令限制请求正文的大小。应用此配置后,可以比较API网关在收到向定价服务发出的两个不同“PATCH”请求时的行为。第一个“curl”命令发送一小段JSON数据,而第二个命令则尝试发送一个大文件(/etc/services)的内容。
$ curl -iX PATCH -d '{"price":199.99}' https://api.example.com/api/warehouse/pricing/item001HTTP/1.1 204 No Content
Server: nginx/1.19.5
Connection: keep-alive
$ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001
HTTP/1.1 413 Request Entity Too Large
Server: nginx/1.19.5
Content-Type: application/json
Content-Length: 45
Connection: close
{"status":413,"message":"Payload too large"}验证请求正文
[编辑:以下用例是NGINX JavaScript模块的多个用例之一。有关完整列表,请参阅NGINX JavaScript模块用例。]
除了可能因请求正文过大而遭受缓冲区溢出攻击外,后端API服务还可能受到包含无效或意外数据的请求正文影响。对于要求请求正文中的JSON格式正确的应用,可以使用NGINX JavaScript module<.htmla>,在将请求代理到后端API服务之前,验证JSON数据能否正确解析。
安装JavaScript模块后,我们使用js_import指令引用包含JSON数据验证函数JavaScript代码的文件。
正在加载代码示例…
The js_set指令定义了一个新变量$json_validated,该变量通过调用parseRequestBody函数求值。
正在加载代码示例…
The parseRequestBody函数尝试使用JSON.parse方法解析请求正文(第6行)。如果解析成功,则返回此请求的目标上游组名称(第8行)。如果无法解析请求正文(导致异常),则返回本地服务器地址(第11行)。return指令会填充$json_validated变量,以便用它确定将请求发送到何处。
正在加载代码示例…
在Warehouse API的URI路由部分,我们修改了第22行的proxy_pass指令。与前几节讨论的Warehouse API配置一样,该指令会将请求传递给后端API服务,但现在使用$json_validated变量作为目标地址。如果客户端请求正文成功解析为JSON,我们会将请求代理到第15行定义的上游组。但如果发生异常,则使用返回值127.0.0.1:10415向客户端发送错误响应。
正在加载代码示例…
当请求被代理到此虚拟服务器时,NGINX会向客户端发送“415 (不支持的 媒体 类型)”响应。
完成此配置后,NGINX仅代理JSON正文格式正确的请求,并将其转发到后端API服务。
$ curl -iX POST -d '{"sku":"item002","price":85.00}' https://api.example.com/api/warehouse/pricingHTTP/1.1 201 Created
Server: nginx/1.19.5
Location: /api/warehouse/pricing/item002
$ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing
{"status":415,"message":"Unsupported media type"}关于$request_body变量的说明
JavaScript函数“parseRequestBody”使用“$request_body”变量执行JSON解析。但是,NGINX默认不会填充此变量,而是直接将请求正文以流式方式传输到后端,不创建中间副本。通过在URI路由部分(第16行)使用“mirror”指令,可以创建客户端请求的副本,从而填充“$request_body”变量。
正在加载代码示例…
第17行和第19行的指令控制NGINX在内部处理请求正文的方式。我们将client_body_buffer_size设置为与client_max_body_size相同的大小,以避免将请求正文写入磁盘。这样可以最大限度减少磁盘I/O操作,从而提升整体性能,但会占用更多内存。对于请求正文较小的大多数API网关用例而言,这是一种合理的折中方案。
如前所述,mirror指令会创建客户端请求的副本。除了用于填充$request_body外,我们不需要此副本,因此将其发送到一个“死胡同”位置(/_get_request_body),该位置在顶层API网关配置的server块中定义。
正在加载代码示例…
此 location 仅发送204 (无 内容)响应。由于此响应与镜像请求相关,因此会被忽略,对原始客户端请求处理造成的额外开销可忽略不计。
总结
在本系列介绍如何将NGINX Open Source和NGINX Plus部署为API网关的第2篇博文中,我们重点探讨了如何在生产环境中保护后端API服务,使其免受恶意客户端和行为异常客户端的影响。NGINX采用相同的技术来管理API流量,该技术也用于为当今互联网上流量繁忙的网站提供支持和保护。
查看本系列的另一篇文章:
要试用NGINX Plus作为API网关,请立即开始30天免费试用,或联系我们,讨论您的用例。试用期间,请使用我们GitHub Gist代码库中的完整配置文件集。
关于作者

相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
