如何使用GraphQL实现基于资源的授权?

时间:2019-06-25 07:21:56

标签: c# graphql asp.net-core-2.0 mediator mediatr

实施基于资源的授权的最佳实践是什么。在REST API中,给每个终结点分配角色非常简单,但是GraphQL的作用如何。只有一个端点可以处理所有请求。

示例: 如果数据库结构如下所示:

表:用户 列:ID,登录名,名称,描述,年龄,收藏夹主题等。

每个请求都到达相同的端点。 用户应该能够查询其所有数据以及其他部分用户数据。 当用户在请求中发送自己的ID(用户ID也在JWT中发送,但是当他也在请求中发送ID时,则两个ID都可以匹配),那么他应该能够在所有表列中查询自己的ID帐户。但是,当他发送另一个用户ID(JTW ID和发送的ID剂量匹配)时,该用户应该只能查询一些公共字段,例如favorite_topic左右。

我从microsoft找到了一个文档,其中基于角色的授权有点解释,但是不支持GraphQL

https://docs.microsoft.com/de-de/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.2

我在这里也发现了关于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);
                }
            );



        }

有什么我想念的东西可以帮助我解决这种情况吗?

0 个答案:

没有答案