英文原版地址:https://www.howtographql.com/...git
GraphQL为客户端提供强大的能力。可是拥有强大的能力时,也会带来更大的风险。github
因为客户端有可能使用很是复杂的查询,所以咱们的服务器必须可以妥善处理。这些查询多是来自恶意客户端的滥用查询,或者可能只是合法客户端使用的很是大的查询。在这两种状况下,客户端可能会将您的GraphQL服务器崩溃。算法
咱们将在本章中介绍一些减轻这些风险的策略。咱们将以最简单到最复杂的顺序来讲明,并看看这些方式的利弊。ruby
第一个策略,也是最简单的策略是使用简单的超时来防范大型查询。这不须要服务器了解有关传入查询的任何内容。服务器须要知道的仅仅是容许查询的最长时间。服务器
例如,配置了5秒超时的服务器将中止执行超过5秒钟执行的任何查询。架构
操做简单ide
大多数策略都会使用超时做为最终保护post
即便有超时策略,也可能会形成很差的后果ui
有时难以实施。在一段时间以后切断链接可能会致使奇怪的行为。spa
正如咱们以前所述,使用GraphQL的客户能够随意写出任意的复杂查询。因为GraphQL模式一般是嵌套的,这意味着客户端能够写出以下所示的查询:
query IAmEvil { author(id: "abc") { posts { author { posts { author { posts { author { # that could go on as deep as the client wants! } } } } } } } }
若是咱们能够阻止客户滥用这样的查询深度呢? 在了解定义的模式时,可让你了解合法查询的深度。这其实是能够实现的,而且一般称为最大查询深度。
经过分析查询文档的AST,GraphQL服务器可以根据其深度拒绝或接受请求。
例如,配置了最大查询深度为3的服务器,以及如下查询文档。红色方框选中的全部内容都被认为深度太深,查询无效。
使用最大查询深度设置的graphql-ruby服务,咱们获得如下返回结果:
{ "errors": [ { "message": "Query has depth of 6, which exceeds max depth of 3" } ] }
因为静态分析了文档的AST,所以查询甚至不执行,因此不会在GraphQL服务器上增长负担。
只有深度每每不足以涵盖全部滥用查询。 例如,在根节点上请求大量的查询将是代价巨大的,但不太可能被查询深度分析器阻止。
有时,查询的深度还不足以真正了解GraphQL查询的开销。在不少状况下,咱们的模式中的某些字段比其余字段更复杂。
查询复杂性容许您定义这些字段的复杂程度,并限制最大复杂度的查询。这个想法是经过使用一个简单的数字来定义每一个字段的复杂程度。一个常见的默认设置是给每一个字段一个复杂的1。以这个查询为例:
query { author(id: "abc") { # complexity: 1 posts { # complexity: 1 title # complexity: 1 } } }
一个简单的加法,告诉咱们查询的复杂性是3。若是咱们在咱们的架构上设置最大复杂度为2,则此查询将会失败。
若是posts字段实际上比做者字段复杂度高不少呢?咱们能够为该领域设置不一样的复杂性。咱们甚至能够根据参数设置不一样的复杂性! 咱们来看看一个相似的查询,其中posts会根据传入的参数去肯定复杂性:
query { author(id: "abc") { # complexity: 1 posts(first: 5) { # complexity: 5 title # complexity: 1 } } }
能够覆盖比更多的用例。
经过静态分析复杂性,在执行前拒绝查询。
很难实现完美
若是须要开发时预估复杂性,咱们如何保持状态最新?咱们一开始怎么能知道查询成本?
Mutations 很难估计。若是他们有一个难以衡量的附加操做,如在后台排队执行的任务怎么办?
到目前为止,咱们看到的解决方案都是会阻止滥用服务器的查询。像这样使用它们的问题是,它们会阻止大量查询,但不会阻止客户端生成出大量查询!
在大多数API中,使用简单的节流方式是,阻止客户端频繁地请求资源。GraphQL有点特别,由于调节请求数并无真正帮助咱们。即便是不多的请求也多是大量的查询。
事实上,咱们不知道客户端定义了多少请求是能够接受的。那么咱们如何来限制客户端呢?
咱们能够经过查询执行时的服务器耗时,来估计查询的复杂程度。咱们可使用这种式来限制查询。凭借对系统的了解,您能够提出客户端能够在特定时间范围内使用的最大服务器时间。
咱们还决定随着时间的推移,客户端添加多少服务器时间。这是一个经典的leaky bucket 算法。请注意,还有其余节流算法,但这些算法超出了本章的范围。在下面的例子中咱们将使用leaky bucket。
让咱们想象一下,咱们将容许的最大服务器时间(Bucket Size)设置为1000ms,客户端每秒得到100ms的服务器时间(Leak Rate),mutation 以下:
mutation { createPost(input: { title: "GraphQL Security" }) { post { title } } }
这个mutation平均须要200ms才能完成。实际上,时间可能会有所不一样,但咱们假设为了这个例子,它老是须要200ms才能完成。
这意味着在1秒内调用此操做超过5次的客户端将被阻止,直到更多的可用服务器时间添加到客户端。
通过两秒钟(100ms加秒),咱们的客户能够一次调用createPost。
正如你所看到的,基于时间的调节是限制GraphQL查询不错的方式,由于复杂的查询将最终消耗更多的时间,这意味着你不能频繁地调用它们,而较小的查询可能被更频繁地调用,由于它们将很是快速地计算。
但若是GraphQL API是公开的,向客户端提出这些限制条件就不那么容易了。在这种状况下,服务器耗时并不能很好地告知客户端,客户端也不能准确的估计他们的查询所须要的时间,在不先试着请求的状况下。
还记得咱们以前提到的最大复杂度?若是咱们根据这个调节,会怎么样?
基于查询复杂度的调节是与客户端合做的好方法,客户端能够遵循schema中的限制。
咱们使用与“查询复杂性”部分中使用的相同的复杂性示例:
query { author(id: "abc") { # complexity: 1 posts { # complexity: 1 title # complexity: 1 } } }
咱们知道这个查询的成本是基于复杂度的3。就像时间流逝同样,咱们能够得知客户可使用的每次最高成本(Bucket Size)。
若是最大成本为9,咱们的客户只能三次运行此查询,不容许查询更多。
这些原理与咱们的时间节制相同,但如今将这些限制传达给客户端后。客户甚至能够本身计算查询成本,而无需估计服务器时间!
GitHub公共API实际上使用这种方法来扼制客户端。看看他们如何对用户表达这些限制:https://developer.github.com/v4/guides/resource-limitations/。
GraphQL很是适合用于客户端,由于给予了更多的功能。可是,强大的功能也带来了风险,担忧客户端会以很是昂贵的查询来滥用GraphQL服务器。
有许多方法来保护您的GraphQL服务器免受这些查询,可是它们都不是万无一失的。重要的是,咱们要知道有哪些方法可用来限制,并了解他们的优缺点,而后采起最优的决定!