授权
¥Authorization
Delegate authorization logic to the business logic layer
大多数 API 都需要根据请求者来保护对某些类型数据的访问,GraphQL 也不例外。在 authentication 中间件确认用户身份并将该信息传递给 GraphQL 层后,GraphQL 执行应该开始。但在此之后,你仍需要确定是否允许经过身份验证的用户查看请求中包含的特定字段提供的数据。在此页面上,我们将探索 GraphQL 模式如何支持授权。
¥Most APIs will need to secure access to certain types of data depending on who requested it, and GraphQL is no different. GraphQL execution should begin after authentication middleware confirms the user’s identity and passes that information to the GraphQL layer. But after that, you still need to determine if the authenticated user is allowed to view the data provided by the specific fields that were included in the request. On this page, we’ll explore how a GraphQL schema can support authorization.
类型和字段授权
¥Type and field authorization
授权是一种业务逻辑,描述给定的用户/会话/上下文是否有权执行操作或查看数据。例如:
¥Authorization is a type of business logic that describes whether a given user/session/context has permission to perform an action or see a piece of data. For example:
“只有作者才能看到他们的草稿”
¥“Only authors can see their drafts”
强制执行此行为应在 业务逻辑层 中发生。让我们考虑在架构中定义的以下 Post
类型:
¥Enforcing this behavior should happen in the business logic layer. Let’s consider the following Post
type defined in a schema:
type Post {
authorId: ID!
body: String
}
在这个例子中,我们可以想象,当请求最初到达服务器时,身份验证中间件将首先检查用户的凭据,并将有关其身份的信息添加到 GraphQL 请求的 context
对象中,以便这些数据在执行期间在每个字段解析器中都可用。
¥In this example, we can imagine that when a request initially reaches the server, authentication middleware will first check the user’s credentials and add information about their identity to the context
object of the GraphQL request so that this data is available in every field resolver for the duration of its execution.
如果帖子的正文应该只对作者可见,那么我们需要检查经过身份验证的用户的 ID 是否与帖子的 authorId
值匹配。将授权逻辑放置在帖子的 body
字段的解析器中可能很有吸引力,如下所示:
¥If a post’s body should only be visible to the user who authored it, then we will need to check that the authenticated user’s ID matches the post’s authorId
value. It may be tempting to place authorization logic in the resolver for the post’s body
field like so:
function Post_body(obj, args, context, info) {
// return the post body only if the user is the post's author
if (context.user && (context.user.id === obj.authorId)) {
return obj.body
}
return null
}
请注意,我们通过检查帖子的 authorId
字段是否等于当前用户的 id
来定义 “作者拥有一篇文章”。你能找出问题所在吗?我们需要为服务的每个入口点复制此代码。然后,如果授权逻辑没有完全保持同步,用户可能会根据他们使用的 API 看到不同的数据。哎呀!我们可以通过使用 单一事实来源 进行授权而不是将其放在 GraphQL 层来避免这种情况。
¥Notice that we define “author owns a post” by checking whether the post’s authorId
field equals the current user’s id
. Can you spot the problem? We would need to duplicate this code for each entry point into the service. Then if the authorization logic is not kept perfectly in sync, users could see different data depending on which API they use. Yikes! We can avoid that by having a single source of truth for authorization, instead of putting it the GraphQL layer.
在学习 GraphQL 或原型设计时,在解析器内定义授权逻辑是很好的。但是,对于生产代码库,请将授权逻辑委托给业务逻辑层。以下是如何单独实现 Post
类型字段授权的示例:
¥Defining authorization logic inside the resolver is fine when learning GraphQL or prototyping. However, for a production codebase, delegate authorization logic to the business logic layer. Here’s an example of how authorization of the Post
type’s fields could be implemented separately:
// authorization logic lives inside `postRepository`
export const postRepository = {
getBody({ user, post }) {
if (user?.id && (user.id === post.authorId)) {
return post.body
}
return null
}
}
然后,帖子的 body
字段的解析器函数将调用 postRepository
方法,而不是直接实现授权逻辑:
¥The resolver function for the post’s body
field would then call a postRepository
method instead of implementing the authorization logic directly:
import { postRepository } from 'postRepository'
function Post_body(obj, args, context, info) {
// return the post body only if the user is the post's author
return postRepository.getBody({ user: context.user, post: obj })
}
在上面的例子中,我们看到业务逻辑层要求调用者提供一个用户对象,该对象在 GraphQL 请求的 context
对象中可用。我们建议将完全水合的用户对象而不是不透明令牌或 API 密钥传递给你的业务逻辑层。这样,我们就可以在请求处理管道的不同阶段处理 authentication 和授权的不同问题。
¥In the example above, we see that the business logic layer requires the caller to provide a user object, which is available in the context
object for the GraphQL request. We recommend passing a fully-hydrated user object instead of an opaque token or API key to your business logic layer. This way, we can handle the distinct concerns of authentication and authorization in different stages of the request processing pipeline.
使用类型系统指令
¥Using type system directives
在上面的例子中,我们看到了如何通过在字段解析器中调用的函数将授权逻辑委托给业务逻辑层。一般来说,建议在该层中执行所有授权逻辑,但如果你决定在 GraphQL 层中实现授权,那么一种方法是使用 类型系统指令。
¥In the example above, we saw how authorization logic can be delegated to the business logic layer through a function that is called in a field resolver. In general, it is recommended to perform all authorization logic in that layer, but if you decide to implement authorization in the GraphQL layer instead then one approach is to use type system directives.
例如,可以在架构中定义诸如 @auth
之类的指令,并使用参数指示用户必须具有哪些角色或权限才能访问应用该指令的类型和字段提供的数据:
¥For example, a directive such as @auth
could be defined in the schema with arguments that indicate what roles or permissions a user must have to access the data provided by the types and fields where the directive is applied:
directive @auth(rule: Rule) on FIELD_DEFINITION
enum Rule {
IS_AUTHOR
}
type Post {
authorId: ID!
body: String @auth(rule: IS_AUTHOR)
}
当客户端发出包含 Post
类型的 body
字段的请求时,GraphQL 实现将决定 @auth
指令如何影响执行。但是,授权逻辑应保留委托给业务逻辑层。
¥It would be up to the GraphQL implementation to determine how an @auth
directive affects execution when a client makes a request that includes the body
field for Post
type. However, the authorization logic should remain delegated to the business logic layer.
回顾
¥Recap
回顾 GraphQL 中授权的这些建议:
¥To recap these recommendations for authorization in GraphQL:
-
授权逻辑应该委托给业务逻辑层,而不是 GraphQL 层
¥Authorization logic should be delegated to the business logic layer, not the GraphQL layer
-
执行开始后,GraphQL 服务器应该决定发出请求的客户端是否有权访问所包含字段的数据
¥After execution begins, a GraphQL server should make decisions about whether the client that made the request is authorized to access data for the included fields
-
可以定义类型系统指令并将其添加到模式中的类型和字段以应用通用授权规则
¥Type system directives may be defined and added to the types and fields in a schema to apply generalized authorization rules