【问题标题】:Authentication and Access Control with Relay使用中继的身份验证和访问控制
【发布时间】:2016-02-06 15:48:27
【问题描述】:

Facebook 的官方说法是 Relay 是“intentionally agnostic about authentication mechanisms”。在 Relay 存储库中的所有示例中,身份验证和访问控制是一个单独的问题。在实践中,我还没有找到一种简单的方法来实现这种分离。

Relay 存储库中提供的示例都具有带有viewer 字段的根模式,假设只有一个用户。该用户可以访问所有内容。

然而,实际上,一个应用程序有很多用户,每个用户对每个节点的访问权限都不同。

假设我在 JavaScript 中有这个架构:

export const Schema = new GraphQLSchema({
    query: new GraphQLObjectType({
        name: 'Query',
        fields: () => ({
            node: nodeField,
            user: {
                type: new GraphQLObjectType({
                    name: 'User',
                    args: {
                        // The `id` of the user being queried for
                        id: { type: new GraphQLNonNull(GraphQLID) },
                        // Identity the user who is querying
                        session: { type: new GraphQLInputObjectType({ ... }) },
                    },
                    resolve: (_, { id, session }) => {
                        // Given `session, get user with `id`
                        return data.getUser({ id, session });
                    }
                    fields: () => ({
                        name: {
                            type: GraphQLString,
                            resolve: user => {
                                // Does `session` have access to this user's
                                // name?
                                user.name
                            }
                        }
                    })
                })
            }
        })
    })
});

从查询用户的角度来看,某些用户是完全私密的。其他用户可能只向查询用户公开某些字段。因此,要获取用户,客户端不仅必须提供他们正在查询的用户 ID,而且还必须标识自己,以便进行访问控制。

这似乎很快就变得复杂了,因为控制访问的需求会逐渐渗透到图表中。

此外,我需要控制每个根查询的访问权限,例如 nodeField。我需要确保每个节点都实现了nodeInterface

所有这些似乎都是重复性的工作。是否有任何已知的模式可以简化这一点?我是不是想错了?

【问题讨论】:

  • 我认为如果 Relay 中有一些中间件位于执行引擎之上并根据会话信息重写查询 AST,那将是非常酷的。
  • 你有没有得到一个很好的例子/答案?我正在寻找有关使用中继的令牌身份验证(无会话)的信息,但很难找到任何东西
  • @GreenRails 不在这里,但我想出了如何去做。这很不错!基本上,对我来说,关键是弄清楚你可以将东西放入 GraphQL “rootValue”,它在所有分辨率级别都可用。如果您使用的是 express 中间件,可以这样完成:gist.github.com/dminkovsky/…。任何实现都可以这样做。然后,根据下面的答案,您还可以采用“面向查看器”的方法来加载数据以协助 ACL。 github.com/facebook/dataloader 是一个很好的辅助工具。
  • @GreenRails 刚刚添加了答案

标签: javascript graphql relayjs graphql-js


【解决方案1】:

如果有人对此主题有疑问:我根据 dimadima 的回答为 Relay/GraphQL/express 身份验证做了一个示例 repo。它使用 express 中间件和 GraphQL Mutation 将会话数据(用户 ID 和角色)保存在 cookie 中

【讨论】:

    【解决方案2】:

    我发现,如果您使用 GraphQL rootValue,处理身份验证很容易,当针对模式执行查询时,它会传递给执行引擎。此值在所有执行级别都可用,并且对于存储访问令牌或任何标识当前用户的内容很有用。

    如果您使用的是 express-graphql 中间件,您可以将会话加载到 GraphQL 中间件之前的中间件中,然后配置 GraphQL 中间件以将该会话放入根值中:

    function getSession(req, res, next) {
      loadSession(req).then(session => {
        req.session = session;
        next();
      }).catch(
        res.sendStatus(400);
      );
    }
    
    app.use('/graphql', getSession, graphqlHTTP(({ session }) => ({
      schema: schema,
      rootValue: { session }
    })));
    

    然后,此会话在架构中的任何深度都可用:

    new GraphQLObjectType({
      name: 'MyType',
      fields: {
        myField: {
          type: GraphQLString,
          resolve(parentValue, _, { rootValue: { session } }) {
            // use `session` here
          }
        }
      }
    });
    

    您可以将此与“面向查看器”的数据加载配对以实现访问控制。查看https://github.com/facebook/dataloader,它有助于创建这种数据加载对象并提供批处理和缓存。

    function createLoaders(authToken) {
      return {
        users: new DataLoader(ids => genUsers(authToken, ids)),
        cdnUrls: new DataLoader(rawUrls => genCdnUrls(authToken, rawUrls)),
        stories: new DataLoader(keys => genStories(authToken, keys)),
      };
    }
    

    【讨论】:

    • 你觉得这种方法与休息相比如何?
    • @GreenRails 我不是很有经验。也许其他人可以回答这个问题?
    【解决方案3】:

    不同的应用程序对访问控制的形式有非常不同的要求,因此在基本的 Relay 框架或 GraphQL 参考实现中加入一些东西可能没有意义。

    我见过的一种非常成功的方法是将隐私/访问控制融入数据模型/数据加载器框架。每次你加载一个对象,你不会只是通过 id 加载它,还要提供查看器的上下文。如果查看者看不到该对象,它将无法加载就好像它不存在,以防止甚至泄露对象的存在。该对象还保留查看器上下文,并且某些字段可能具有在从对象返回之前检查的受限访问。在较低级别的数据加载机制中进行烘焙有助于确保较高级别产品/GraphQL 代码中的错误不会泄露私有数据。

    在一个具体的例子中,我可能不被允许看到某个用户,因为他阻止了我。一般情况下,您可能会被允许看到他,但不能看到他的电子邮件,因为您不是他的朋友。

    在代码中是这样的:

    var viewer = new Viewer(getLoggedInUser());
    User.load(id, viewer).then(
      (user) => console.log("User name:", user.name),
      (error) => console.log("User does not exist or you don't have access.")
    )
    

    尝试在 GraphQL 级别实现可见性有很大可能泄露信息。想想在 Facebook 的 GraphQL 实现中访问用户的多种方式:

    node($userID) { name }
    node($postID) { author { name } }
    node($postID) { likers { name } }
    node($otherUserID) { friends { name } }
    

    所有这些查询都可以加载用户名,如果用户阻止了您,它们都不应该返回用户或用户名。对所有这些字段进行访问控制并且不要忘记在任何地方进行检查是错过检查的秘诀某处

    【讨论】:

    • 应该有一个 github repo 来证明这一点。
    • 这不是一个具体的例子。您已经回答了访问控制(这只是添加了一些逻辑),但没有触及身份验证
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-25
    • 2013-01-02
    • 2023-03-27
    • 2012-12-23
    • 1970-01-01
    • 1970-01-01
    • 2020-07-17
    相关资源
    最近更新 更多