如何使用 Asp.Net Core 实现基于权限的访问控制

How to implement Permission Based Access Control with Asp.Net Core

我正在尝试使用 aspnet core 实现基于权限的访问控制。为了动态管理用户角色和权限(create_product、delete_product 等),它们存储在数据库中。数据模型就像 http://i.stack.imgur.com/CHMPE.png

在使用 aspnet core(在 MVC 5 中)之前,我使用如下自定义 AuthorizeAttribute 来处理问题:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    private readonly string _permissionName { get; set; }
    [Inject]
    public IAccessControlService _accessControlService { get; set; }

    public CustomAuthorizeAttribute(string permissionName = "")
    {
        _permissionName = permissionName;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        var user = _accessControlService.GetUser();
        if (PermissionName != "" && !user.HasPermission(_permissionName))
        {
            // set error result
            filterContext.HttpContext.Response.StatusCode = 403;
            return;
        }
        filterContext.HttpContext.Items["CUSTOM_USER"] = user;
    }
}

然后我在下面的操作方法中使用它:

[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }

此外,我在视图中使用 HttpContext.Items["CUSTOM_USER"] 来显示或隐藏 html 部分:

@if (CurrentUser.HasPermission("<Permission Name>"))
{

}

当我决定切换aspnet core时,我所有的计划都失败了。因为AuthorizeAttribute里面没有virtualOnAuthorization方法。我尝试了一些方法来解决问题。这些如下:

我无法决定哪种方式最适合我的场景以及如何实现它。

第一个问题:MVC5 实现是否是不好的做法?

第二个问题:你有什么实现aspnet core的建议吗?

根据评论,这里有一个关于如何使用基于策略的授权的示例:

public class PermissionRequirement : IAuthorizationRequirement
{
    public PermissionRequirement(PermissionEnum permission)
    {
         Permission = permission;
    }

    public PermissionEnum Permission { get; }
}

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
    private readonly IUserPermissionsRepository permissionRepository;

    public PermissionHandler(IUserPermissionsRepository permissionRepository)
    {
        if(permissionRepository == null)
            throw new ArgumentNullException(nameof(permissionRepository));

        this.permissionRepository = permissionRepository;
    }

    protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
    {
        if(context.User == null)
        {
            // no user authorizedd. Alternatively call context.Fail() to ensure a failure 
            // as another handler for this requirement may succeed
            return null;
        }

        bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
        if (hasPermission)
        {
            context.Succeed(requirement);
        }
    }
}

并在您的 Startup class 中注册:

services.AddAuthorization(options =>
{
    UserDbContext context = ...;
    foreach(var permission in context.Permissions) 
    {
        // assuming .Permission is enum
        options.AddPolicy(permission.Permission.ToString(),
            policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
    }
});

// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();

最后在控制器中

[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
    ...
}

这个解决方案的优点是你也可以有多个处理程序来满足一个要求,也就是说,如果第一个处理程序成功,第二个处理程序可以确定它失败了,你可以用 resource based authorization 来使用它,而不需要额外的努力.

基于策略的方法是 ASP.NET 核心团队的首选方法。

来自

We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.

对于不需要您为每个权限添加策略的解决方案,请参阅我的 的另一个问题。

它允许您使用您想要的任何自定义属性来装饰您的控制器和操作,并在您的 AuthorizationHandler 中访问它们。

我有同样的要求,我已经按照下面的方式完成了,对我来说效果很好。我正在使用 .Net Core 2.0 Webapi

[AttributeUsage(AttributeTargets.Class | 
                         AttributeTargets.Method
                       , AllowMultiple = true
                       , Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
  private string[] _permission;
  public CheckAccessAttribute(params string[] permission)
  {
      _permission = permission;
  }

  public void OnAuthorization(AuthorizationFilterContext context)
  {
     var user = context.HttpContext.User;

     if (!user.Identity.IsAuthenticated)
     {
        return;
     }

     IRepository service = 
     (IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
     var success = service.CheckAccess(userName, _permission.ToList());
     if (!success)
     {
        context.Result = JsonFormatter.GetErrorJsonObject(
                               CommonResource.error_unauthorized,
                               StatusCodeEnum.Forbidden);
        return;
     }
     return;
   }
}

在 Controller 中使用它如下

[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
   // your code api code here.
}