安全性
¥Security
Protect GraphQL APIs from malicious operations
与任何类型的 API 一样,你需要考虑在请求执行期间应使用哪些安全措施来保护 GraphQL 实现的服务器资源和底层数据源。
¥As with any type of API, you will need to consider what security measures should be used to protect a GraphQL implementation’s server resources and underlying data sources during request execution.
许多可推广的 API 安全最佳实践适用于 GraphQL,尤其是与给定实现所选的传输协议相关的。但是,GraphQL 特定的安全注意事项也将使 API 层更能抵御潜在攻击。
¥Many generalizable API security best practices apply to GraphQL, especially in relation to a given implementation’s selected transport protocol. However, there are GraphQL-specific security considerations that will make the API layer more resilient to potential attacks too.
在此页面上,我们将调查 GraphQL 的潜在攻击媒介(其中许多是拒绝服务攻击),以及分层安全态势如何帮助保护 GraphQL API 免受恶意操作的侵害。
¥On this page, we’ll survey potential attack vectors for GraphQL—many of which are denial of service attacks—along with how a layered security posture can help protect a GraphQL API from malicious operations.
传输层安全性
¥Transport layer security
GraphQL 规范不需要针对请求使用特定的传输协议,但需要针对无状态查询和变异操作使用 HTTP 是一种流行的选择。对于较长时间的订阅操作,通常使用 WebSocket 或服务器发送的事件。无论选择哪种协议,传输层中的安全考虑都可以为 GraphQL API 提供重要的第一道防线。
¥The GraphQL specification does not require a specific transport protocol for requests, but HTTP is a popular choice for stateless query and mutation operations. For longer-lived subscription operations, WebSockets or server-sent events are often used. Whatever the chosen protocol, security considerations in the transport layer can provide an important first line of defense for a GraphQL API.
通常,用于使用给定传输协议提供的任何类型的 API 的安全措施也应用于 GraphQL。例如,当使用 HTTP 进行查询和变异时,你应该使用 HTTPS 加密数据,为请求设置适当的超时时间,并且,如果使用 HTTP 缓存,请确保敏感数据被私下缓存(或根本不缓存)。
¥The same security measures that would be used for any type of API served with a given transport protocol should typically be used for GraphQL as well. For example, when using HTTP for queries and mutations, you should use HTTPS to encrypt data, set appropriate timeout durations for requests, and, if using HTTP caching, ensure sensitive data is cached privately (or not at all).
需求控制
¥Demand control
受信任的文档
¥Trusted documents
对于仅为第一方客户端提供服务的 GraphQL API,你可以在开发环境中根据需要编写操作,然后锁定这些操作,以便只有它们可以由生产中的前端应用发送。具体来说,使用受信任的文档将允许你创建可针对架构执行的操作的允许列表。
¥For GraphQL APIs that only serve first-party clients, you can write operations as needed in development environments and then lock those operations so that only they may be sent by front-end apps in production. Specifically, using trusted documents will allow you to create an allowlist of operations that can be executed against a schema.
作为开发过程中的构建步骤,客户端可以将其 GraphQL 文档提交到服务器的允许列表,其中每个文档都存储有一个唯一的 ID - 通常是它的哈希值。在运行时,客户端可以发送文档 ID 而不是完整的 GraphQL 文档,并且服务器将仅执行已知文档 ID 的请求。
¥As a build step during development clients may submit their GraphQL documents to the server’s allowlist, where each document is stored with a unique ID—usually its hash. At runtime, the client can send a document ID instead of the full GraphQL document, and the server will only execute requests for known document IDs.
受信任的文档只是持久化的文档,被认为不是恶意的,通常是因为它们是由你的开发者编写并通过代码审查批准的。将持久文档标准化为 GraphQL-over-HTTP 规范的一部分 的努力正在进行中 - 越来越多的 GraphQL 客户端和服务器已经支持它们(有时在其遗留的误称下:持久查询)。
¥Trusted documents are simply persisted documents that are deemed to not be malicious, usually because they were authored by your developers and approved through code review. Efforts are underway to standardize persisted documents as part of the GraphQL-over-HTTP specification—an increasing number of GraphQL clients and servers already support them (sometimes under their legacy misnomer: persisted queries).
请记住,受信任的文档不能用于公共 API,因为第三方客户端发送的操作不会提前知道。但是,以下其他建议可以帮助保护公共 GraphQL API 免受恶意操作的侵害。
¥Keep in mind that trusted documents can’t be used for public APIs because the operations sent by third-party clients won’t known in advance. However, the other recommendations that follow can help defend a public GraphQL API from malicious operations.
分页字段
¥Paginated fields
实现 GraphQL API 需求控制的第一步是限制可以从单个字段查询的数据量。当字段输出 List 类型并且结果列表可能会返回大量数据时,应该对其进行分页以限制单个请求中可能返回的最大项目数。
¥A first step toward implementing demand control for a GraphQL API is limiting the amount of data that can be queried from a single field. When a field outputs a List type and the resulting list could potentially return a lot of data, it should be paginated to limit the maximum number of items that may be returned in a single request.
例如,在此操作中,将 friends
字段的无界输出与 friendsConnection
字段返回的有限数据进行比较:
¥For example, compare the unbounded output of the friends
field to the limited data returned by the friendsConnection
field in this operation:
阅读有关 分页页面 上基于连接的分页字段模型的更多信息。
¥Read more about the connection-based model for paginating fields on the Pagination page.
深度限制
¥Depth limiting
GraphQL 的优势之一是客户端可以编写富有表现力的操作来反映 API 中公开的数据之间的关系。但是,当选择集中的字段嵌套很深时,某些查询可能会变成周期性的:
¥One of GraphQL’s strengths is that clients can write expressive operations that reflect the relationships between the data exposed in the API. However, some queries may become cyclical when the fields in the selection set are deeply nested:
query {
hero {
name
friends {
name
friends {
name
friends {
name
friends {
name
}
}
}
}
}
}
即使通过对底层数据源的批量请求修复了 N+1 问题,过度嵌套的字段仍可能对服务器资源造成过重负载并影响 API 性能。
¥Even when the N+1 problem has been remediated through batched requests to underlying data sources, overly nested fields may still place excessive load on server resources and impact API performance.
因此,限制单个操作可以具有的最大字段深度是一个好主意。许多 GraphQL 实现都公开了配置选项,允许你指定 GraphQL 文档的最大深度,并在请求在执行开始前超过此限制时向客户端返回错误。
¥For this reason, it’s a good idea to limit the maximum depth of fields that a single operation can have. Many GraphQL implementations expose configuration options that allow you to specify a maximum depth for a GraphQL document and return an error to the client if a request exceeds this limit before execution begins.
由于嵌套列表字段会导致返回的数据量呈指数级增长,因此建议对列表的嵌套深度应用单独的较小限制。
¥Since nesting list fields can result in exponential increases to the amount of data returned, it’s recommended to apply a separate smaller limit to how deeply lists can be nested.
对于客户端可能对深度嵌套查询具有合法用例并且对所有查询设置低限度不切实际的情况,你可能需要依赖 速率限制 或 查询复杂性分析 等技术。
¥For cases where a client may have a legitimate use case for a deeply nested query and it’s impractical to set a low blanket limit on all queries, you may need to rely on techniques such as rate limiting or query complexity analysis.
广度和批次限制
¥Breadth and batch limiting
除了限制操作深度之外,还应该有护栏来限制单个操作中包含的顶层字段和字段别名的数量。
¥In addition to limiting operation depth, there should also be guardrails in place to limit the number of top-level fields and field aliases included in a single operation.
考虑在执行以下查询操作期间会发生什么:
¥Consider what would happen during the execution of the following query operation:
query {
viewer {
friends1: friends(limit: 1) { name }
friends2: friends(limit: 2) { name }
friends3: friends(limit: 3) { name }
# ...
friends100: friends(limit: 100) { name }
}
}
即使此查询的整体深度很浅,API 的底层数据源仍必须处理大量请求以解析别名 hero
字段的数据。
¥Even though the overall depth of this query is shallow, the underlying data source for the API will still have to handle a large number of requests to resolve data for the aliased hero
field.
类似地,客户端可以在请求中发送包含许多批处理操作的 GraphQL 文档:
¥Similarly, a client may send a GraphQL document with many batched operations in a request:
query NewHopeHero {
hero(episode: NEWHOPE) {
name
}
}
query EmpireHero {
hero(episode: EMPIRE) {
name
}
}
### ...
query JediHero {
hero(episode: JEDI) {
name
}
}
根据客户端实现,查询批处理可以成为一种有用的策略,用于限制往返服务器的次数以获取呈现用户界面所需的所有数据。但是,单个批次中允许的查询总数应该有一个上限。
¥Depending on the client implementation, query batching can be a useful strategy for limiting the number of round trips to a server to fetch all of the required data to render a user interface. However, there should be an upper limit on the total number of queries allowed in a single batch.
与深度限制一样,GraphQL 实现可能具有配置选项来限制操作广度、字段别名使用和批处理。
¥As with depth limiting, a GraphQL implementation may have configuration options to restrict operation breadth, field alias usage, and batching.
速率限制
¥Rate limiting
深度、广度和批次限制有助于防止广泛的恶意操作类别,例如循环查询和批处理攻击,但它们不提供声明特定字段在计算上解析成本高昂的方法。因此,对于更高级的需求控制要求,你可能希望实现速率限制。
¥Depth, breadth, and batch limiting help prevent broad categories of malicious operations such as cyclic queries and batching attacks, but they don’t provide a way to declare that a particular field is computationally expensive to resolve. So for more advanced demand control requirements, you may wish to implement rate limiting.
速率限制可能发生在应用的不同层中,例如在网络层或业务逻辑层中。由于 GraphQL 允许客户端准确指定他们在查询中需要的数据,因此服务器可能无法提前知道请求是否包含会在执行期间对其资源造成更高负载的字段。因此,对 GraphQL API 应用有用的速率限制通常需要一种不同于简单地跟踪网络层中一段时间内传入请求总数的方法;因此,通常建议在业务逻辑层内应用速率限制。
¥Rate limiting may take place in different layers of an application, for example, in the network layer or the business logic layer. Because GraphQL allows clients to specify exactly what data they need in their queries, a server may not be able to know in advance if a request includes fields that will place a higher load on its resources during execution. As a result, applying useful rate limits for a GraphQL API typically requires a different approach than simply keeping track of the total number of incoming requests over a time period in the network layer; therefore applying rate limits within the business logic layer is generally recommended.
查询复杂性分析
¥Query complexity analysis
通过对模式中的类型和字段应用权重,你可以使用称为查询复杂性分析的技术来估计传入请求的成本。如果请求中包含的字段组合超出了每个请求的最大允许成本,你可以选择直接拒绝该请求。估计成本也可以计入速率限制:如果请求继续,则可以从分配给客户端特定时间段的总体查询预算中扣除请求的总成本。
¥By applying weights to the types and fields in a schema, you can estimate the cost of incoming requests using the technique known as query complexity analysis. If the combination of fields included in a request exceeds a maximum allowable cost per request, you may choose to reject the request outright. The estimated cost can also be factored into rate limiting: if the request proceeds, the total cost of the request can be deducted from the overall query budget allocated to a client for a specific time period.
虽然 GraphQL 规范没有提供任何关于为 API 实现查询复杂性分析或速率限制的指南,但有 社区维护的草案规范 用于实现支持这些计算的自定义类型系统指令。
¥While the GraphQL specification doesn’t provide any guidelines on implementing query complexity analysis or rate limits for an API, there is a community-maintained draft specification for implementing custom type system directives that support these calculations.
架构注意事项
¥Schema considerations
验证和清理参数值
¥Validating and sanitizing argument values
GraphQL 是强类型的,请求的 验证阶段 将根据类型系统检查已解析的文档以确定其有效性。但是,在某些情况下,你可能会发现内置的标量类型不足以确保客户端提供的参数值适合安全地执行字段解析器。
¥GraphQL is strongly typed, and the validation stage of a request will check a parsed document against the type system to determine its validity. However, for certain cases you may find that the built-in Scalar types aren’t enough to make sure that the argument value a client provides is appropriate to execute a field resolver safely.
考虑与 createReview
突变一起使用的以下 ReviewInput
类型:
¥Consider the following ReviewInput
type that is used with the createReview
mutation:
input ReviewInput {
stars: Int!
commentary: String
}
type Mutation {
createReview(episode: Episode, review: ReviewInput!): Review
}
根据客户端应用将提交和显示评论数据的类型,可能需要清理为 commentary
字段提供的字符串,以防用户在其中提交不安全的 HTML。
¥Depending on what kind of client application will submit and display the review data, it may be necessary to sanitize the string provided for the commentary
field in case a user submits unsafe HTML in it.
清理步骤应在执行 createReview
解析器函数期间调用的业务逻辑层中进行。为了向前端开发者展示这一点,也可以使用自定义 标量类型 将其表示为架构的一部分。
¥The sanitization step should take place in the business logic layer that is called during execution of the createReview
resolver function. To surface this to frontend developers, it could also be expressed as a part of the schema using a custom Scalar type.
内省
¥Introspection
内省 是 GraphQL 的一个强大功能,它允许你查询 GraphQL API 以获取有关其架构的信息。但是,仅为第一方客户端提供服务的 GraphQL API 可能会禁止在非开发环境中进行自省查询。
¥Introspection is a powerful feature of GraphQL that allows you to query a GraphQL API for information about its schema. However, GraphQL APIs that only serve first-party clients may forbid introspection queries in non-development environments.
请注意,禁用自省可以用作 “通过隐蔽性实现安全性,” 的一种形式,但本身不足以完全掩盖 GraphQL API。它可以用作更广泛的安全策略的一部分,以限制潜在攻击者发现 API 信息的可发现性。为了更全面地保护敏感架构详细信息和用户数据,应使用受信任的文档和授权。
¥Note that disabling introspection can be used as a form of “security through obscurity,” but will not be sufficient to entirely obscure a GraphQL API by itself. It may be used as a part of a broader security strategy to limit the discoverability of API information from potential attackers. For more comprehensive protection of sensitive schema details and user data, trusted documents and authorization should be used.
错误消息
¥Error messages
在 响应页面 上,我们看到 GraphQL 实现可以为开发者提供有关请求中的验证错误的有用信息,以及如何解决这些错误的建议。例如:
¥On the Response page, we saw that a GraphQL implementation may provide developers with useful information about validation errors in a request, along with suggestions for how these errors may be addressed. For example:
这些提示在调试客户端错误时很有用,但它们可能提供有关生产环境中架构的更多信息,而我们不想透露这些信息。在开发环境之外隐藏 GraphQL 响应中的详细错误信息非常重要,因为即使禁用自省,攻击者最终也可以通过运行大量具有不正确字段名称的操作来推断整个模式的形状。
¥These hints can be helpful when debugging client-side errors, but they may provide more information about a schema in a production environment than we would like to reveal. Hiding detailed error information in GraphQL responses outside of development environments is important because, even with introspection disabled, an attacker could ultimately infer the shape of an entire schema by running numerous operations with incorrect field names.
除了请求错误之外,还应屏蔽字段执行期间引发的错误的详细信息,因为它们可能会泄露有关服务器或底层数据源的敏感信息。
¥In addition to request errors, details about errors that are raised during field execution should be masked as they may reveal sensitive information about the server or underlying data sources.
身份验证和授权
¥Authentication and authorization
在 授权页面 上深入讨论了与 GraphQL API 相关的身份验证注意事项。
¥Auth-related considerations for GraphQL APIs are discussed in-depth on the Authorization page.
回顾
¥Recap
回顾这些关于保护 GraphQL API 的建议:
¥To recap these recommendations for securing a GraphQL API:
-
所选传输协议的一般安全注意事项通常也适用于 GraphQL API
¥General security considerations for a chosen transport protocol will usually apply to a GraphQL API as well
-
根据你的要求,需求控制可以在 GraphQL 中以多种方式实现,包括受信任的文档、列表字段的分页、深度限制、广度/批次限制和速率限制
¥Depending on your requirements, demand control can be implemented in many ways in GraphQL, including trusted documents, pagination of list fields, depth limiting, breadth/batch limiting, and rate limiting
-
GraphQL 模式可以帮助支持对客户端提供的值的验证和清理
¥A GraphQL schema can help support validation and sanitization of client-provided values
-
禁用自省和隐藏错误信息会使 API 的模式更难被发现,但单独使用它们通常是不够的(文档允许列表可能是更有效的解决方案)
¥Disabling introspection and obscuring error information can make an API’s schema less discoverable, but they will typically not be sufficient when used on their own (a document allowlist is likely to be a more effective solution)