不同用户角色的基于权限的授权

时间:2020-12-23 07:41:20

标签: c# asp.net-core authorization microservices asp.net-core-webapi

我正在 Dot Net Core 3.1 中开发一个 WebAPI 微服务项目。我正在 Azure AD 中对用户进行身份验证。我从 Azure AD 收到用户的 jwt 令牌。这个 Jwt 令牌以及其他东西也包含这样的角色:

"roles": [
    "Engineer"
  ],

我有一个数据库,其中包含与之关联的角色和权限列表。根据与角色关联的权限,用户可以访问 Put/Patch/Get API。比如说,管理员可以访问 PUT 和 Patch API,而普通用户(角色 - 工程师)只能访问 Get API。

我如何根据用户所拥有的角色和权限对其进行授权。任何建议、想法、代码示例或参考文章都会很有帮助。

谢谢...

编辑:我不想要确切的答案,只是关于我应该如何解决这个问题的要点或指导。我在互联网上寻找解决方案 2 3 天,但找不到任何有意义的东西,这就是为什么我在这里问。

我将提供更多细节:我提到了几个链接: Permission based Authorization

此处作者按角色对声明进行分组。他正在使用身份框架来创建角色和添加声明。但我不能使用它,因为我是用 AD 登录 jwt 令牌来做的。另外,为了从 jwt 令牌中获取我的角色,我需要 HTTPContext,我无法在任何类中访问它,到目前为止,它只能在 API Controller 类中访问,我可以在其中使用以下代码获取角色:

User.Identities.SelectMany(s => s.Claims).Where(s => s.Type.Contains("role")).Select(s => s.Value).FirstOrDefault()

因此,一旦我掌握了角色,我将对我的数据库服务进行 httpclient get 调用,以获取与之相关的所有角色和权限。但是,我如何动态授权我的 API 控制器 Rest 调用?

1 个答案:

答案 0 :(得分:0)

我想出了几种解决难题的方法。一种方法是通过 Custom Authorization Policy Providers using IAuthorizationPolicyProvider

在上面的方法中,我会创建像

这样的自定义策略属性
[MinimumAccessAuthorize("Read","ModuleName1")]

[MinimumAccessAuthorize("Write","ModuleName2")]

在我的 Web API 操作方法之上,IAuthorizationPolicyProvider 将负责其余的工作。这种方法的唯一问题是,我需要为以后每个控制器中的每个上帝坝操作方法的模块提供读/写访问的硬编码属性。

解决这个问题的另一种方法是提出一个自定义中间件,在请求到达控制器之前对用户进行身份验证。

所以我创建了一个自定义中间件:Resource for custom middleware

在我的中间件中,我有一个名为 invokeAsync 的函数

public async Task InvokeAsync(HttpContext context, [FromServices] IRoleService roleService, [FromServices] IPermissionService permissionService)
    {
        var roles = context.User.Identities.SelectMany(s => s.Claims).Where(s => s.Type.Contains("role")).Select(s => s.Value).FirstOrDefault();

        if (roles == null)
        {
            context.Response.StatusCode = 401;
            return;
        }

        var requestType = context.Request.Method;
        var controller = context.Request.Path.Value.Replace("/api/v1/", "").Split('/')[0];


        PermissionType typeRequired = requestType == "GET" ? PermissionType.Read : PermissionType.Write;

        var (IsSuccessRole, roleResult) = await roleService.GetRoleAsync();
        if (!IsSuccessRole)
        {
            context.Response.StatusCode = 500;
            return;
        }
        int _roleId = roleResult.Where(r => r.RoleName == roles).Select(r => r.Id).FirstOrDefault();

        var (IsSuccess, permissionResult) = await permissionService.GetPermissionAsync(_roleId);
        if (!IsSuccess)
        {
            context.Response.StatusCode = 500;
            return;
        }
        var validRoles = from p in permissionResult
                         join k in GetControllerModuleMaps()
                         on p.ProjectModule.ModuleName equals k.ModuleName
                         where k.ControllerName == controller && p.PermissionType >= typeRequired
                         select p.RoleId;

        if (!validRoles.Any())
        {
            context.Response.StatusCode = 401;
            return;
        }
        await next(context);


    }

我正在传递 HTTP 上下文和 2 个 HTTP 客户端服务以从数据库中获取权限和角色数据。

我现在通过上下文和基本 linq 查询检查 jwt 令牌中存在的用户角色。

然后进行基本验证以查看角色是否为空。

我检查请求类型,然后查看它是 GET、PUT 还是 Patch 请求。根据请求,我将值存储为 PermissionType 中的枚举。

我将 REST 请求将命中的控制器详细信息存储在控制器变量中。

现在使用角色服务,我在数据库中查询与我当前的 jwt 令牌角色关联的 roleId。然后我使用权限服务从数据库中获取与该角色 ID 关联的权限。

现在基于从数据库成功查询权限,我编写了一个 Linq 查询语法,我将数据库中的权限结果数据与包含控制器名称和模块名称作为对值的 C# 记录列表连接起来(不是密钥对值)。

记录的示例列表如下所示:

    var toRet = new List<ControllerRecord>();

    toRet.Add(new ControllerRecord("Controller1", "Module 1"));
    toRet.Add(new ControllerRecord("Controller1", "Module 2"));

我检查我的控制器记录是否与请求控制器路径匹配,并且与该角色在数据库中关联的权限大于在 Rest API 中请求的权限(这是有效的,因为我有枚举,所以 NoAccess = 0 < GET 请求 = 仅读取 = 1 < 放置/修补/发布请求 = 写入 = 2 )

如果有任何有效的角色,我会让流程照常继续。

这就是我把它拉过来的方式...