graphql 基于角色的授权
graphql role based authorization
我是 GraphQL 的新手,打算使用 GraphQL 构建解决方案。
一切看起来都很酷,但只关心如何在 GraphQL 服务器中实现基于角色的授权(我正在考虑使用 GraphQL.js/apollo 服务器)
我将有一个包含所有用户的用户 table。在用户 table 中有一个角色字段,其中包含特定用户的角色。查询和变更将根据用户的角色授予。
如何实现这个结构?
谢谢!
我最近使用 GraphQL Shield, I found that using that package was the simplest way to do it. Otherwise you could add custom schema directives, here's a good article on how to do that: 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
函数。
希望对你有所帮助
对于apollo server开发者来说,Graphql中实现授权的方式一般有3种:
基于模式:向要保护的 graphql 类型和字段添加指令
基于中间件:添加中间件(在 graphql 解析器执行之前和之后运行的代码)。这是 graphql-shield and other authorization libraries built on top of graphql-middleware.
使用的方法
业务逻辑层:这是最原始但粒度的方法。基本上,returns 数据(即数据库查询等)的功能将实现自己的 permissions/authorization 检查。
基于模式
- 使用基于模式的授权,我们将定义自定义模式指令并在适用的地方应用它们。
来源: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
}
});
基于中间件
- 使用基于中间件的授权,大多数库都会拦截解析器的执行。以下示例特定于
apollo-server
上的 graphql-shield
。
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!'));
业务逻辑层
- 使用业务逻辑层授权,我们将在我们的解析器逻辑中添加权限检查。这是最乏味的,因为我们必须在每个解析器上编写授权检查。下面的link建议将授权逻辑放在业务逻辑层(即有时称为'Models'或'Application logic'或'data-returning function')。
来源: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')
}
}
我是 GraphQL 的新手,打算使用 GraphQL 构建解决方案。
一切看起来都很酷,但只关心如何在 GraphQL 服务器中实现基于角色的授权(我正在考虑使用 GraphQL.js/apollo 服务器)
我将有一个包含所有用户的用户 table。在用户 table 中有一个角色字段,其中包含特定用户的角色。查询和变更将根据用户的角色授予。
如何实现这个结构?
谢谢!
我最近使用 GraphQL Shield, I found that using that package was the simplest way to do it. Otherwise you could add custom schema directives, here's a good article on how to do that: 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
函数。
希望对你有所帮助
对于apollo server开发者来说,Graphql中实现授权的方式一般有3种:
基于模式:向要保护的 graphql 类型和字段添加指令
基于中间件:添加中间件(在 graphql 解析器执行之前和之后运行的代码)。这是 graphql-shield and other authorization libraries built on top of graphql-middleware.
使用的方法业务逻辑层:这是最原始但粒度的方法。基本上,returns 数据(即数据库查询等)的功能将实现自己的 permissions/authorization 检查。
基于模式
- 使用基于模式的授权,我们将定义自定义模式指令并在适用的地方应用它们。
来源: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
}
});
基于中间件
- 使用基于中间件的授权,大多数库都会拦截解析器的执行。以下示例特定于
apollo-server
上的graphql-shield
。
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!'));
业务逻辑层
- 使用业务逻辑层授权,我们将在我们的解析器逻辑中添加权限检查。这是最乏味的,因为我们必须在每个解析器上编写授权检查。下面的link建议将授权逻辑放在业务逻辑层(即有时称为'Models'或'Application logic'或'data-returning function')。
来源: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')
}
}