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

How Can I Extend ASP.NET Core 2.0's Roles to Handle Different Permissions at Different Locations?

我们正在 Asp.net 核心 2.0 中实施一个新的 Web 应用程序,我希望能够根据事物的组合而不是一个特定的用户角色(如管理员、高级用户等)。问题 space 看起来像这样:

目前,我们使用的是一种无法控制权限的自制解决方案,但它仅适用于用户 'home' 设施。例如,我们不能授权从其他设施订购库存的人查看不同设施的库存。

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

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

您可以创建一个 AuthorizationFilterAttribute 并将其分配给每个 API 端点或控制器 class。这将允许您为每个用户分配 case-by-case 权限,然后您只需要一个包含特定权限 ID 的 table。

这是一个从基本身份验证中提取用户名的实现。您可以通过更改 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();
    }

我建议使用政策。他们给了你很大的 finer-grained 控制权。基本上,您从一个或多个 "requirements" 开始。例如,您可以从以下内容开始:

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")]

当然,这是一个非常基本的示例。您可以制作可以处理多种不同需求的处理程序。您可以进一步构建您的要求,因此您也不需要那么多。或者您可能更喜欢更明确,并为每个特定场景设置 requirements/handlers。这完全取决于你。

有关详细信息,请参阅 documentation