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

Posted

技术标签:

【中文标题】如何使用 Asp.Net Core 实现基于权限的访问控制【英文标题】:How to implement Permission Based Access Control with Asp.Net Core 【发布时间】:2016-07-26 12:58:06 【问题描述】:

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

在 aspnet 核心(在 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 核心时,我的所有计划都失败了。因为AuthorizeAttribute 中没有虚拟的OnAuthorization 方法。我尝试了一些方法来解决问题。如下:

使用新的基于策略的授权(我认为它不适合 我的场景)

使用自定义 AuthorizeAttributeAuthorizationFilter(我读过这个 发布https://***.com/a/35863514/5426333,但我无法正确更改)

使用自定义中间件(如何获取当前的AuthorizeAttribute 行动?)

使用 ActionFilter(出于安全目的是否正确?)

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

第一个问题:MVC5 实施是不好的做法吗?

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

【问题讨论】:

为什么您认为基于策略的授权不适合您的情况?您仍然可以创建 PermissionRequirement 实现 IAuthorizationRequirement 和一个处理程序,然后将其添加为 options.AddPolicy("PersonList", policy =&gt; policy.Requirements.Add(new PermissionRequirement("PersonList"))); 因为,我想从数据库中获取用户权限。 不,这是 1 个需求和 1 个处理程序(尽管一个需求可以由多个处理程序处理)。您只需要 500 个策略注册,您可以在循环中添加它们 我对答案添加了一个小更新。如果services.AddSingleton&lt;IAuthorizationHandler, PermissionHandler&gt;(); 使用DbContext,则services.AddSingleton&lt;IAuthorizationHandler, PermissionHandler&gt;(); 应该是services.AddScope&lt;IAuthorizationHandler, PermissionHandler&gt;();,因为DbContext 是并且应该根据请求进行解析,而不是在应用程序的生命周期内解析 “您只需要 500 个策略注册”。这对我来说似乎很麻烦(想象一下预先存在的 .NET MVC 完整框架应用程序,它是具有多个后端数据库的多租户环境,只有在该租户的第一个请求进入时才能访问)。可以将事情更改为使用基于策略的方法,但对于旧世界中使用自定义授权属性非常简单的事情来说,这似乎非常迂回。 【参考方案1】:

基于cmets,这里有一个关于如何使用基于策略的授权的例子:

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 类中注册它:

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 Core 团队的首选方法。

来自blowdart:

我们不希望您编写自定义授权属性。如果你需要这样做,我们做错了什么。相反,您应该编写授权要求。

【讨论】:

使用 asp.net core rc1 我不能使用 [Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())] 因为编译器要求在属性声明中只使用“常量”。转机是使用这样的属性:[Authorize(Policy = nameof(PermissionEnum.PERSON_LIST))] 您将如何修改此解决方案以应对基于资源的规则?例如,我需要检查用户是否具有基于枚举值的权限以及仅在执行控制器操作时可用的某些信息?那是必须注入 IAuthorizationService 的地方吗? @Sam 你不会在Handle 方法中做额外的检查吗? 我无法检查未直接传递给 Handle 方法的信息。如果我需要检查仅在控制器操作级别范围内的附加信息,并且我希望它可以作为参数提供给处理程序的 Handle 方法,我必须直接调用 IAuthorizationService 并将附加信息作为对象传递,以便它获取也传递给处理程序 - 这不能使用声明性策略来完成,这就是我要问的。 @AlanAraya 另一个非常简单的解决方案是实现您自己的MyAuthorizeAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute。类中唯一的代码需要是这样的构造函数:public MyAuthorizeAttribute(PermissionEnum perm) =&gt; Policy = perm.ToString(); 我没有想过使用 nameof,但我确实喜欢这种方式,一目了然更容易阅读。【参考方案2】:

我有同样的要求,我已经按照下面的方法完成了,它对我来说很好。我正在使用.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.

【讨论】:

请注意,如果您的OnAuthorization 实现需要等待异步方法,您应该实现IAsyncAuthorizationFilter 而不是IAuthorizationFilter 否则您的过滤器将同步执行并且您的控制器操作将执行而不管结果如何过滤器。 service.checkAccess 中发生了什么?【参考方案3】:

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

它可以让你用你想要的任何自定义属性来装饰你的控制器和动作,并在你的 AuthorizationHandler 中访问它们。

【讨论】:

我非常喜欢这个解决方案。它非常干净和灵活。 您能否将链接添加到您的答案中?

以上是关于如何使用 Asp.Net Core 实现基于权限的访问控制的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Web Api Asp.Net Core 实现基于声明的授权?

Asp.Net Core 2.0 项目实战NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架

Asp.net core IdentityServer4与传统基于角色的权限系统的集成

Asp.NET Core一个接口的多个实现如何基于当前HTTP请求注册

使用 ASP.NET Core Identity 3 的用户角色权限

具有用户权限的基于 JWT 令牌的授权 Asp.net core 2.0