Для разработчиков серверов apollo обычно существует 3 способа реализовать авторизацию в Graphql:
На основе схемы: добавление директивы к типам и полям graphql, которые вы хотите защитить.
На основе промежуточного программного обеспечения: добавление промежуточного программного обеспечения (кода, который запускается до и после выполнения преобразователей graphql). Это подход, используемый graphql-shield и другими библиотеками авторизации, созданными на основе graphql-middleware.
Уровень бизнес-логики: это наиболее примитивный, но детальный подход. По сути, функция, возвращающая данные (например, запрос к базе данных и т. Д.), Будет реализовывать свою собственную проверку разрешений / авторизации.
На основе схемы
- С помощью авторизации на основе схемы мы определяем настраиваемые директивы схемы и применяем их везде, где это применимо.
Источник: 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
}
});
На основе промежуточного программного обеспечения
- При авторизации на основе промежуточного программного обеспечения большинство библиотек перехватывают выполнение преобразователя. Приведенный ниже пример относится к
graphql-shield
на apollo-server
.
Источник Graphql-shield: https://github.com/maticzav/graphql-shield
Реализация для источника apollo-server: 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!'));
Слой бизнес-логики
- С помощью авторизации на уровне бизнес-логики мы добавили бы проверки разрешений внутри нашей логики преобразователя. Это наиболее утомительно, потому что нам пришлось бы писать проверки авторизации для каждого резолвера. В приведенной ниже ссылке рекомендуется разместить логику авторизации на уровне бизнес-логики (т. Е. Иногда называемую «Модели», «Логика приложения» или «функция возврата данных»).
Источник: 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')
}
}
person
David Choy
schedule
11.12.2019