如何扩展ASP.NET Core 2.0的角色以处理不同位置的不同权限?

问题描述 投票:2回答:2

我们正在Asp.net core 2.0中实现一个新的Web应用程序,我希望能够基于一组事物来限制操作,而不是一个特定的用户角色(如管理员,高级用户等)。问题空间如下所示:

  • 每个用户都可以拥有一个特定的“家庭”设施,他们根据工作职能拥有默认权限。
  • 每个CRUD操作都有与之关联的特定权限。
  • 每个许可都可以在任意数量的设施中授予,只有一个,或者根本没有(或者它们的某种组合)。
  • 特定用户可以在不同的设施具有不同的权限。例如,区域经理可以在他们使用的所有工厂中拥有“查看”和“订单”权限,但只能拥有相邻区域中工厂的“查看”权限。

目前,我们使用的是本土解决方案,这种解决方案已失控以限制权限,但它仅适用于用户的家庭设施。我们无法授予从其他工厂订购库存的人员,例如,查看其他工厂的库存。

我们试图仅为每个设施中的每个操作应用角色(Yikes!),这些角色是动态生成的,但这会导致某些用户获得他们不应拥有的权限。更不用说,这是一个维持的噩梦。

如何在ASP.NET Core 2.0中扩展角色功能,以允许我的用户在不同的设施中拥有不同的权限,而无需为每个设施的每个操作创建角色?

asp.net-core-2.0 asp.net-core-webapi user-permissions user-profile
2个回答
3
投票

我建议使用政策。它们为您提供更精细的控制。基本上,您从一个或多个“要求”开始。例如,您可以从以下内容开始:

public class ViewFacilitiesRequirement : IAuthorizationRequirement
{
}

public class OrderFacilitiesRequirement : IAuthorizationRequirement
{
}

这些主要用作授权处理程序的附件,因此它们非常基本。肉来自那些授权处理程序,您可以在其中定义符合要求的实际含义。

public class ViewFacilitiesHandler : AuthorizationHandler<ViewFacilitiesRequirement>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewFacilitiesRequirement requirement)
    {
        // logic here, if user is authorized:
        context.Succeed(requirement);
    }
}

授权处理程序是依赖注入的,因此您可以以正常方式将DbContextUserManager<TUser>等内容注入其中,然后查询这些源以确定用户是否已获得授权。

一旦你有了一些要求和处理程序,你需要注册它们:

services.AddAuthorization(o =>
{
    o.AddPolicy("ViewFacilities", p =>
        p.Requirements.Add(new ViewFacilitiesRequirement()));
});

services.AddScoped<IAuthorizationHandler, ViewFacilitiesHandler>();

如果不明显,策略可以使用多个要求。所有人都必须通过该政策才能通过。处理程序只需要在DI容器中注册。它们根据它们适用的要求类型自动应用。

然后,在需要此权限的控制器或操作上:

 [Authorize(Policy = "ViewFacilities")]

当然,这是一个非常基本的例子。您可以制作处理程序,而不是处理多种不同的要求。您可以更多地构建您的需求,因此您也不需要那么多。或者您可能更喜欢更明确,并且针对每个特定方案都有需求/处理程序。这完全取决于你。

有关更多详细信息,请参阅documentation


1
投票

您可以创建AuthorizationFilterAttribute并将其分配给每个API端点或Controller类。这将允许您为每个用户分配逐个案例的权限,然后您只需要一个包含特定权限ID的表。

这是一个从基本身份验证中提取用户名的实现。您可以通过更改OnAuthorization方法来从您的实现更改身份验证,以从存储它的任何位置检索用户详细信息。

/// <summary>
/// Generic Basic Authentication filter that checks if user is allowed 
/// to access a specific permssion.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter: AuthorizationFilterAttribute
{
    private readonly bool _active = true;
    private readonly int _permissionId;

    public BasicAuthenticationFilter(int permissionId)
    {
        private _permissionId = permissionId;
    }

    /// <summary>
    /// Overriden constructor to allow explicit disabling of this
    /// filter's behavior. Pass false to disable (same as no filter
    /// but declarative)
    /// </summary>
    public BasicAuthenticationFilter(bool active) => _active = active;

    /// <summary>
    /// Override to Web API filter method to handle Basic Authentication.
    /// </summary>
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (!_active) return;

        BasicAuthenticationIdentity identity = ParseAuthorizationHeader(actionContext);
        if (identity == null && !OnAuthorizeUser(identity.Name, identity.Password, actionContext))
        {
            Challenge(actionContext);
            return;
        }

        Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
        base.OnAuthorization(actionContext);

    }

    /// <summary>
    /// Base implementation for user authentication you'll want to override this implementing
    /// requirements on a case-by-case basis.
    /// </summary>
    protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
    {
        if (!Authorizer.Validate(username,password)) // check if user is authentic
            return false;

        using (var db = new DbContext())
        {
            var userPermissions = _context.UserPermissions
                .Where(user => user.UserName == username);

            if (userPermissions.Permission.Contains(_permissionId))
                return true;
            else
                return false;
            return true;
        }
    }

    /// <summary>
    /// Parses the Authorization header and creates user credentials
    /// </summary>
    protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
    {
        string authHeader = null;
        System.Net.Http.Headers.AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
        if (auth?.Scheme == "Basic")
            authHeader = auth.Parameter;

        if (String.IsNullOrEmpty(authHeader))
            return null;

        authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));

        string[] tokens = authHeader.Split(':');
        if (tokens.Length < 2)
            return null;

        return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
    }

    /// <summary>
    /// Send the Authentication Challenge request
    /// </summary>
    private void Challenge(HttpActionContext actionContext)
    {
        var host = actionContext.Request.RequestUri.DnsSafeHost;
        actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        actionContext.Response.Headers.Add("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", host));
    }
}

然后,您只需将过滤器添加到API方法中:

    private const int _orderPermission = 450;
    /// <summary>
    /// Submit a new order.
    /// </summary>
    [HttpPost, BasicAuthenticationFilter(_orderPermission)]
    public void Submit([FromBody]OrderData value)
    {
        Task.Run(()=> ProcessInbound());

        return Ok();
    }
© www.soinside.com 2019 - 2024. All rights reserved.