最近一直在写一些graphql方面的东西, 在做项目的过程中会有一些是关于权限管理的部分,于是发现了一个十分优雅的方式来做权限方面的处理.
简而言之, 先讲下需求背景, 该项目是一个商城,商城会有三个角色,分别为ROOT(管理员)
, MERCHANT(商户)
,CUSTOMER(用户)
每个角色能够访问和控制的资源显然是不一样的,在过去的项目中,我们可能会使用一些Validator来校验当前请求的用户是否具备相应的权限,而在Graphql这套体系中,我们可以使用Directive 更加声明式地来鉴权
我们先来看个Directive来鉴权的sample
"鉴权指令"
directive @auth(requires: Role = CUSTOMER) on OBJECT | FIELD_DEFINITION
extend type Mutation {
"用户删除"
deleteUser(userDeleteInput: UserDeleteInput!): UserActionResult!
@auth(requires: ROOT)
}
通过上面的@auth(requires: ROOT)
我们就可以很轻松地限制仅ROOT
权限才可以进行删除用户的行为, 这样就能够很声明式地去做权限管理部分的功能,同时对于开发者而言,也能够很清晰地看到哪个部分的资源需要什么样子的权限才能进行访问
既然我们可以这么优雅地干权限管理这间事情, 那么怎么在Apollo Graphql中实现?
首先我们需要先看下如何实现一个指令相关文档点这里
import { SchemaDirectiveVisitor } from 'graphql-tools';
import { defaultFieldResolver } from 'graphql';
import { Role } from '../status';
// 继承Apollo Graphql提供的SchemaDirectiveVisitor
export class AuthDriective extends SchemaDirectiveVisitor {
/**
* 此处便是定义了访问filed的方法
*/
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
};
}
}
我们可以看到在此处可以拿到对应的filed的resolve方法,那只需要将resolve进行改造,在调用resolve之前进行鉴权即可
import { SchemaDirectiveVisitor } from 'graphql-tools';
import { defaultFieldResolver } from 'graphql';
import { Role } from '../status';
export class AuthDriective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
const { requires } = this.args;
field.resolve = function resolver(...args) {
const context = args[2];
if (!context.currentUser || context.currentUser.role < Role[requires]) {
throw new Error('账号权限不足');
}
return resolve.apply(this, args);
};
}
}
在上面的代码中,我们通过this.args可以获取到指令当前的参数,也就是requires
, 然后我们通过重新定义filed.resolve, 在进入业务流程前进行权限校验, context
是上下文的意思,通过context
, 我们可以知道当前的用户的相关信息,如果校验鉴权失败,就抛出对应的鉴权错误
指令部分的实现先到这里,我们还有一件事情需要处理 将用户信息设置到context中
import { tradeTokenForUser } from '../encode';
export const setGraphqlContext = ({ ctx: { request, session } }) => {
let authToken = null;
let currentUser = null;
try {
// 获取token
authToken = request.headers.authorization;
if (authToken) {
// 解开token获取当前请求的用户信息
currentUser = tradeTokenForUser(authToken);
}
} catch (e) {
throw e;
}
return {
authToken,
currentUser,
};
};
同时要在 Apollo中注入,还有别忘了 指令
new ApolloServer({
context: setGraphqlContext,
schemaDirectives: {
auth: AuthDriective,
},
})
完成上面的步骤,最后我们还需要在声明下指令
"鉴权指令"
directive @auth(requires: Role = CUSTOMER) on OBJECT | FIELD_DEFINITION
好的,整个Apollo Graphql 到这里就完成了, 后续的话,我在更新一个demo的链接就完美了