graphql基于角色的授权

graphql基于角色的授权,graphql,apollo,express-graphql,Graphql,Apollo,Express Graphql,我是GraphQL新手,将使用GraphQL构建一个解决方案 一切看起来都很酷,但只关心如何在GraphQL服务器中实现基于角色的授权(我正在考虑使用GraphQL.js/apollo服务器) 我将有一个包含所有用户的用户表。在users表中有一个roles字段,其中包含特定用户的角色。将根据用户的角色授予查询和更改 如何实现此结构 谢谢 我最近通过使用实现了基于角色的授权,我发现使用该软件包是最简单的方式。否则,您可以添加自定义模式指令,下面是一篇关于如何做到这一点的好文章: 设置GraphQ

我是GraphQL新手,将使用GraphQL构建一个解决方案

一切看起来都很酷,但只关心如何在GraphQL服务器中实现基于角色的授权(我正在考虑使用GraphQL.js/apollo服务器)

我将有一个包含所有用户的用户表。在users表中有一个roles字段,其中包含特定用户的角色。将根据用户的角色授予查询和更改

如何实现此结构


谢谢

我最近通过使用实现了基于角色的授权,我发现使用该软件包是最简单的方式。否则,您可以添加自定义模式指令,下面是一篇关于如何做到这一点的好文章:

设置GraphQL Shield需要执行以下几个步骤:

1-编写一个身份验证函数,下面是一个粗略的示例,您需要做的远不止此,即使用JWTs而不传递id:

export const isAdmin = async ({ id }) => {
  try {
    const exists = await ctx.db.exists.User({
      id: userId,
      role: 'ADMIN',
    });

    return exists
  } catch (err) {
    console.log(err);
    return false
  }
}
2-在导出所有突变和查询的文件中添加检查:

const resolvers = {
 ...your queries and mutations
}

const permissions = {
   Query: {
     myQuery: isAdmin
   }
}

export default shield(resolvers, permissions);
现在,每次请求查询时,
isAdmin
功能都将启动


我希望这对apollo服务器开发人员有所帮助,在Graphql中实现授权通常有3种方法:

  • 基于模式的:向要保护的graphql类型和字段添加指令

  • 基于中间件的:添加中间件(在执行graphql解析器之前和之后运行的代码)。这是构建在上的和其他授权库所使用的方法

  • 业务逻辑层:这是最原始但最精细的方法。基本上,返回数据的函数(即数据库查询等)将实现自己的权限/授权检查

  • 基于模式
  • 使用基于模式的授权,我们将定义自定义模式指令,并在任何适用的地方应用它们
  • 资料来源:

    //schema.gql

    directive @auth(
      requires: Role = ADMIN,
    ) on OBJECT | FIELD_DEFINITION
    
    enum Role {
      ADMIN
      REVIEWER
      USER
      UNKNOWN
    }
    
    type User @auth(requires: USER) {
      name: String
      banned: Boolean @auth(requires: ADMIN)
      canPost: Boolean @auth(requires: REVIEWER)
    }
    
    //main.js

    class AuthDirective extends SchemaDirectiveVisitor {
      visitObject(type) {
        this.ensureFieldsWrapped(type);
        type._requiredAuthRole = this.args.requires;
      }
    
      visitFieldDefinition(field, details) {
        this.ensureFieldsWrapped(details.objectType);
        field._requiredAuthRole = this.args.requires;
      }
    
      ensureFieldsWrapped(objectType) {
        if (objectType._authFieldsWrapped) return;
        objectType._authFieldsWrapped = true;
    
        const fields = objectType.getFields();
    
        Object.keys(fields).forEach(fieldName => {
          const field = fields[fieldName];
          const { resolve = defaultFieldResolver } = field;
          field.resolve = async function (...args) {
            // Get the required Role from the field first, falling back
            // to the objectType if no Role is required by the field:
            const requiredRole =
              field._requiredAuthRole ||
              objectType._requiredAuthRole;
    
            if (! requiredRole) {
              return resolve.apply(this, args);
            }
    
            const context = args[2];
            const user = await getUser(context.headers.authToken);
            if (! user.hasRole(requiredRole)) {
              throw new Error("not authorized");
            }
    
            return resolve.apply(this, args);
          };
        });
      }
    }
    
    const schema = makeExecutableSchema({
      typeDefs,
      schemaDirectives: {
        auth: AuthDirective,
        authorized: AuthDirective,
        authenticated: AuthDirective
      }
    });
    
    const { ApolloServer, makeExecutableSchema } = require('apollo-server');
    const { applyMiddleware } = require('graphql-middleware');
    const shieldMiddleware = require('shieldMiddleware');
    
    const schema = applyMiddleware(
      makeExecutableSchema({ typeDefs: '...', resolvers: {...} }),
      shieldMiddleware,
    );
    const server = new ApolloServer({ schema });
    app.listen({ port: 4000 }, () => console.log('Ready!'));
    
    基于中间件的
  • 使用基于中间件的授权,大多数库将拦截解析器的执行。以下示例特定于阿波罗服务器上的
    graphql-shield
  • Graphql屏蔽源:

    apollo服务器的实现来源:

    //shield.js

    import { shield, rule, and, or } from 'graphql-shield'
    
    const isAdmin = rule()(async (parent, args, ctx, info) => {
      return ctx.user.role === 'admin'
    })
    
    const isEditor = rule()(async (parent, args, ctx, info) => {
      return ctx.user.role === 'editor'
    })
    
    const isOwner = rule()(async (parent, args, ctx, info) => {
      return ctx.user.items.some(id => id === parent.id)
    })
    
    const permissions = shield({
      Query: {
        users: or(isAdmin, isEditor),
      },
      Mutation: {
        createBlogPost: or(isAdmin, and(isOwner, isEditor)),
      },
      User: {
        secret: isOwner,
      },
    })
    
    //main.js

    class AuthDirective extends SchemaDirectiveVisitor {
      visitObject(type) {
        this.ensureFieldsWrapped(type);
        type._requiredAuthRole = this.args.requires;
      }
    
      visitFieldDefinition(field, details) {
        this.ensureFieldsWrapped(details.objectType);
        field._requiredAuthRole = this.args.requires;
      }
    
      ensureFieldsWrapped(objectType) {
        if (objectType._authFieldsWrapped) return;
        objectType._authFieldsWrapped = true;
    
        const fields = objectType.getFields();
    
        Object.keys(fields).forEach(fieldName => {
          const field = fields[fieldName];
          const { resolve = defaultFieldResolver } = field;
          field.resolve = async function (...args) {
            // Get the required Role from the field first, falling back
            // to the objectType if no Role is required by the field:
            const requiredRole =
              field._requiredAuthRole ||
              objectType._requiredAuthRole;
    
            if (! requiredRole) {
              return resolve.apply(this, args);
            }
    
            const context = args[2];
            const user = await getUser(context.headers.authToken);
            if (! user.hasRole(requiredRole)) {
              throw new Error("not authorized");
            }
    
            return resolve.apply(this, args);
          };
        });
      }
    }
    
    const schema = makeExecutableSchema({
      typeDefs,
      schemaDirectives: {
        auth: AuthDirective,
        authorized: AuthDirective,
        authenticated: AuthDirective
      }
    });
    
    const { ApolloServer, makeExecutableSchema } = require('apollo-server');
    const { applyMiddleware } = require('graphql-middleware');
    const shieldMiddleware = require('shieldMiddleware');
    
    const schema = applyMiddleware(
      makeExecutableSchema({ typeDefs: '...', resolvers: {...} }),
      shieldMiddleware,
    );
    const server = new ApolloServer({ schema });
    app.listen({ port: 4000 }, () => console.log('Ready!'));
    
    业务逻辑层
  • 使用业务逻辑层授权,我们将在解析器逻辑中添加权限检查。这是最乏味的,因为我们必须对每个解析器编写授权检查。下面的链接建议将授权逻辑置于业务逻辑层(即有时称为“模型”或“应用程序逻辑”或“数据返回功能”)
  • 资料来源:

    选项1:解析器中的身份验证逻辑 //resolvers.js

    const Query = {
      users: function(root, args, context, info){
        if (context.permissions.view_users) {
          return ctx.db.query(`SELECT * FROM users`)
        }
        throw new Error('Not Authorized to view users')
      }
    }
    
    选项2(推荐):从解析器中分离授权逻辑 //resolver.js

    const Authorize = require('authorization.js')
    
    const Query = {
      users: function(root, args, context, info){
        Authorize.viewUsers(context)
      }
    }
    
    //authorization.js

    const validatePermission = (requiredPermission, context) => {
      return context.permissions[requiredPermission] === true
    }
    
    const Authorize = {
      viewUsers = function(context){
        const requiredPermission = 'ALLOW_VIEW_USERS'
    
        if (validatePermission(requiredPermission, context)) {
          return context.db.query('SELECT * FROM users')
        }
    
        throw new Error('Not Authorized to view users')
      },
      viewCars = function(context){
         const requiredPermission = 'ALLOW_VIEW_CARS';
    
         if (validatePermission(requiredPermission, context)){
           return context.db.query('SELECT * FROM cars')
         }
    
         throw new Error('Not Authorized to view cars')
      }
    }
    

    GraphQlShield有一些限制,因为如果任何单个权限失败,它往往会阻止/终止整个查询。例如,可用的功能是
    allow/deny
    。它(到目前为止)还不能
    允许某些
    。例如,如果某些权限通过,而另一些权限失败,则不能有选择地允许部分查询成功。