如何检查 GraphQL 查询中的权限和其他条件?

How to check permissions and other conditions in GraphQL query?

我如何检查用户是否有权查看查询 某些内容?我不知道该怎么做。

示例:

如果用户是 "visitor",他只能看到 public 个帖子,"admin" 可以看到所有内容。

const userRole = 'admin';  // Let's say this could be "admin" or "visitor"

const Query = new GraphQLObjectType({
    name: 'Query',
    fields: () => {
        return {
            posts: {
                type: new GraphQLList(Post),
                args: {
                    id: {
                        type: GraphQLString
                    },
                    title: {
                        type: GraphQLString
                    },
                    content: {
                        type: GraphQLString
                    },
                    status: {
                        type: GraphQLInt  // 0 means "private", 1 means "public"
                    },
                },

                // MongoDB / Mongoose magic happens here
                resolve(root, args) {
                    return PostModel.find(args).exec()
                }
            }
        }
    }
})

更新 - Mongoose 模型看起来像这样:

import mongoose from 'mongoose'

const postSchema = new mongoose.Schema({
    title: {
        type: String
    },
    content: {
        type: String
    },
    author: {
        type: mongoose.Schema.Types.ObjectId,  // From user model/collection
        ref: 'User'
    },
    date: {
        type: Date,
        default: Date.now
    },
    status: {
        type: Number,
        default: 0    // 0 -> "private", 1 -> "public"
    },
})

export default mongoose.model('Post', postSchema)

您可以在解析函数或模型层中检查用户的权限。以下是您必须采取的步骤:

  1. 在执行查询之前验证用户。这取决于您的服务器并且通常发生在 graphql 之外,例如通过查看随请求一起发送的 cookie。有关如何使用 Passport.js.
  2. 执行此操作的更多详细信息,请参阅 this Medium post
  3. 将经过身份验证的用户对象或用户 ID 添加到上下文中。在 express-graphql 中,您可以通过上下文参数来完成:

    app.use('/graphql', (req, res) => {
      graphqlHTTP({ schema: Schema, context: { user: req.user } })(req, res);
    }
    
  4. 像这样在 resolve 函数中使用上下文:

    resolve(parent, args, context){
      if(!context.user.isAdmin){
        args.isPublic = true;
      }
      return PostModel.find(args).exec();
    }
    

您可以直接在解析函数中进行授权检查,但如果您有模型层,我强烈建议通过将用户对象传递到模型层来实现它。这样你的代码将更加模块化,更容易重用,你不必担心忘记在某个地方的解析器中进行一些检查。

有关授权的更多背景,请查看此post(也是我自己写的): Auth in GraphQL - part 2

一种帮助我们解决公司授权问题的方法是将解析器视为中间件的组合。上面的例子很好,但它会变得难以控制,尤其是当你的授权机制变得更先进时。

作为中间件组合的解析器示例可能如下所示:

type ResolverMiddlewareFn = 
  (fn: GraphQLFieldResolver) => GraphQLFieldResolver;

A ResolverMiddlewareFn 是一个接受 GraphQLFieldResolver and and returns a GraphQLFieldResolver.

的函数

为了组合我们的解析器中间件函数,我们将使用(您猜对了)组合函数!这是一个 example of compose implemented in javascript, but you can also find compose functions in ramda 和其他功能库。 Compose 让我们可以将简单的函数组合成更复杂的函数。

回到 GraphQL 权限问题,让我们看一个简单的例子。 假设我们要记录解析器,授权用户,然后 运行 肉和土豆。 Compose 让我们可以组合这三个部分,以便我们可以轻松地在我们的应用程序中测试和重用它们。

const traceResolve =
  (fn: GraphQLFieldResolver) =>
  async (obj: any, args: any, context: any, info: any) => {
    const start = new Date().getTime();
    const result = await fn(obj, args, context, info);
    const end = new Date().getTime();
    console.log(`Resolver took ${end - start} ms`);
    return result;
  };

const isAdminAuthorized =
  (fn: GraphQLFieldResolver) =>
  async (obj: any, args: any, context: any, info: any) => {
    if (!context.user.isAdmin) {
      throw new Error('User lacks admin authorization.');
    }
    return await fn(obj, args, context, info);
  }

const getPost = (obj: any, args: any, context: any, info: any) => {
  return PostModel.find(args).exec();
}

const getUser = (obj: any, args: any, context: any, info: any) => {
  return UserModel.find(args).exec();
}

// You can then define field resolve functions like this:
postResolver: compose(traceResolve, isAdminAuthorized)(getPost)

// And then others like this:
userResolver: compose(traceResolve, isAdminAuthorized)(getUser)