实施基于资源的授权的最佳实践是什么。在REST API中,给每个终结点分配角色非常简单,但是GraphQL的作用如何。只有一个端点可以处理所有请求。
示例: 如果数据库结构如下所示:
表:用户 列:ID,登录名,名称,描述,年龄,收藏夹主题等。
每个请求都到达相同的端点。 用户应该能够查询其所有数据以及其他部分用户数据。 当用户在请求中发送自己的ID(用户ID也在JWT中发送,但是当他也在请求中发送ID时,则两个ID都可以匹配),那么他应该能够在所有表列中查询自己的ID帐户。但是,当他发送另一个用户ID(JTW ID和发送的ID剂量匹配)时,该用户应该只能查询一些公共字段,例如favorite_topic左右。
我从microsoft找到了一个文档,其中基于角色的授权有点解释,但是不支持GraphQL
我在这里也发现了关于stackoberflow的问题,但是它们都与graphql没有关系。
因为Microsoft文档没有帮助我解决问题,所以我尝试制作自己的东西。 想法是为每个组和子组创建白名单。 组用户将有两个子组,例如: -User_with_own_id -user_with_strangers_id
这两个子集团都有不同的白名单。白名单可能看起来像这样:
User_Whitelist_Own_ID = new List<string>();
User_Whitelist_Own_ID.Add("id");
User_Whitelist_Own_ID.Add("login_name");
User_Whitelist_Own_ID.Add("games");
User_Whitelist_Own_ID.Add("game");
User_Whitelist_Own_ID.Add("name");
User_Whitelist_Own_ID.Add("user_relations");
User_Whitelist_Own_ID.Add("messages");
User_Whitelist_Own_ID.Add("message");
User_Whitelist_Own_ID.Add("fk_user_account_id_sender");
在这种情况下,白名单只是一个字符串类型的列表,其中包含用户可以或不能查询的所有字段。
使用这样的列表,我可以使用具有递归功能的发送的用户请求查询进行检查,并检查是否允许所有要查询的字段:
public bool CheckGraphQLQueryFieldPermission(List<KeyValuePair<string, GraphQL.Language.AST.Field>> fieldSelection, bool boolUnEvenCount, List<string> fieldWhiteList)
{
int intMatchedWhiteListFields = fieldSelection.Where(x => fieldWhiteList.Contains(x.Key)).ToList().Count();
var intAllFields = fieldSelection.Count();
foreach(KeyValuePair<string, GraphQL.Language.AST.Field> field in fieldSelection)
{
if (intMatchedWhiteListFields != intAllFields || boolUnEvenCount)
{
return boolUnEvenCount = true;
}
int intChildrenCount = field.Value.SelectionSet.Selections.Count();
if (intChildrenCount > 0)
{
boolUnEvenCount = CheckGraphQLQueryFieldPermission(field.Value.GetSelectedFields().ToList(), boolUnEvenCount, fieldWhiteList);
}
}
return false;
}
因为我使用的是MediatR,所以在查询通过之前有一条管道需要执行。那就是我在其中建立这个地方的地方。
要使其完整,管道可能看起来像这样:
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
//get the query context
var context = request.GetType().GetProperty("context").GetValue(request, null);
//get all the asked fields
IHaveSelectionSet fieldAst = (IHaveSelectionSet) context.GetType().GetProperty("FieldAst").GetValue(context, null);
//get the endpoint name
string strEndPointName = (string) context.GetType().GetProperty("FieldName").GetValue(context, null);
//convert all sended arguments into json and then convert to a dictonary
var json = JsonConvert.SerializeObject(context.GetType().GetProperty("Arguments").GetValue(context, null));
Dictionary<string, object> dictArguments = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
switch(_userService.GetUserRole())
{
case RoleEnum.User:
Guid guidTokenUserId = _userService.GetUserId();
if (dictArguments != null && dictArguments["user_id"] != null)
{
Guid guidArgumentUserID = Guid.Parse(dictArguments["user_id"].ToString());
//if the user sends his own id then "do something....", when the user sends the id of
//another user, then check the query with all the whitelists
if (guidTokenUserId == guidArgumentUserID)
{
//do something...
}
else
{
bool boolQueryValidation = CheckGraphQLQueryFieldPermission(fieldAst.GetSelectedFields().ToList(), false, WhiteList);
}
}
break;
}
var response = next();
return response;
}
这工作得很好,但是我怀疑这是正确的解决方案。该解决方案的问题在于,每个组都需要具有自己的白名单的子组,并且每个白名单都必须保存在其保存位置,因为当有人可以设法更改列表时,API就会损坏。另外,一堆字符串类型的列表似乎也不是一个好主意。
现在,用户可以发送这样的查询(假设发送方的ID为1):
{
"query":
"{
user_account{
get_user_account(user_id: 1) {
id,
login_name,
password
}
}
}"
}
此查询应该有效。但是,当ID为1的同一用户发送查询时,他想从另一个用户那里查询数据,如下所示:
{
"query":
"{
user_account{
get_user_account(user_id: 15) {
id,
login_name,
password
}
}
}"
}
不应允许查询执行。
GraphQL端点创建了一个标准点:
public UserAccountGraphType(IQueryBuilder<UserAccount> useraccountQueryBuilder, IMediator _mediator)
{
Name = "user_account";
Field<UserAccountType>(
"get_user_account",
arguments: new QueryArguments(
new QueryArgument<IdGraphType> { Name = "user_id", Description = "The ID of the Author." }),
resolve: context =>
{
//A new Object is created with the current context
//and is send to the meditor/handler (exmp: GetUserAccountHandler)
var getUserAccount = new GetUserAccountQuery() {context = context};
return _mediator.Send(getUserAccount);
}
);
}
有什么我想念的东西可以帮助我解决这种情况吗?