基于graphql角色的授权

时间:2018-02-11 19:46:21

标签: graphql apollo express-graphql

我是GraphQL的新手,并且正在使用GraphQL构建解决方案。

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

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

我该如何实现这个结构?

谢谢!

2 个答案:

答案 0 :(得分:1)

我最近使用GraphQL Shield实现了基于角色的授权,我发现使用该软件包是最简单的方法。否则你可以添加自定义模式指令,这是一篇关于如何做到这一点的好文章:https://dev-blog.apollodata.com/reusable-graphql-schema-directives-131fb3a177d1

设置GraphQL Shield需要采取几个步骤:

1 - 写一个认证函数,这是一个粗略的例子,你要做的远不止这些,即使用JWT而不是传递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函数。

我希望有帮助

答案 1 :(得分:1)

对于apollo服务器开发人员,通常有3种方法可以在Graphql中实现授权:

  1. 基于架构的:向要保护的graphql类型和字段添加指令

  2. 基于中间件的:添加中间件(在执行graphql解析器之前和之后运行的代码)。 graphql-shield和建立在graphql-middleware之上的其他授权库使用的方法。

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

基于架构

  1. 使用基于模式的授权,我们将定义自定义架构指令并在适用的地方应用它们。

来源:https://www.apollographql.com/docs/graphql-tools/schema-directives/

// 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
  }
});

基于中间件

  1. 使用基于中间件的授权,大​​多数库将拦截解析器的执行。以下示例特定于graphql-shield上的apollo-server

Graphql-shield来源:https://github.com/maticzav/graphql-shield

Apollo服务器源的实现:https://github.com/apollographql/apollo-server/pull/1799#issuecomment-456840808

// 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

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. 有了业务逻辑层授权,我们将在解析器逻辑中添加权限检查。这是最繁琐的,因为我们将不得不在每个解析器上编写授权检查。下面的链接建议将授权逻辑放置在业务逻辑层中(即有时称为“模型”或“应用程序逻辑”或“数据返回功能”)。

来源:https://graphql.org/learn/authorization/

选项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')
  }
}