使用处理程序的通用授权

时间:2018-01-26 16:29:03

标签: authorization asp.net-core-2.0

我正在尝试在我的ASP.NET Core 2.0 Web应用程序中实现授权。

这个应用程序有20个模型,每个模型都有一个控制器至少实现一个CRUD。我找到了这些two pages,我喜欢使用处理程序来授权请购单。我想最初由用户实现授权,即用户只有查看/编辑他自己的实体的权限。我的所有数据库实体都有一个OwnerId字段。

我发现这些例子似乎只适用于一个特定的控制器。

所以,我的问题是:是否可以为所有控制器创建一个授权处理程序?

1 个答案:

答案 0 :(得分:1)

您是否找到了可与授权处理程序或授权属性配合使用的解决方案或解决方法?我有与你完全相同的设置。

我正在尝试创建一个通用属性来为所有可能的实体CRUD所有者检查提供服务,但设计中不允许使用通用属性。

我提出的唯一两个(不满意的)解决方案是:

  1. 在控制器操作中,从用户获取ownerId,将其一直转发到CRUD并包含对ownerId的检查。但是,必须为每个控制器中的每个操作复制代码。

    [HttpGet("{id}"]
    public async Task<IActionResult> GetById(int id)
    {
        var stringGuid = User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
        if (String.IsNullOrWhiteSpace(stringGuid)) return Unauthorized();
        var ownerGuid = new Guid(stringGuid);
    
        var entity = _yourCrudInstance.GetById(id, ownerGuid);
    
        return Ok(entity);
    }
    
  2. 将方法添加到CRUD存储库,如bool IsOwner(Guid ownerId),并在创建自定义授权处理程序时使用此方法(通过与自定义处理程序一起创建自定义要求)。这消除了控制器中的代码重复,因为您可以使用此自定义授权处理程序创建新策略,因此您可以使用[Authorize(Policy = "yourOwnershipPolicy")]简单地装饰每个操作。但是,仍然必须为每个控制器创建一个服务。此外,与解决方案1相比,IsOwner(...)方法添加了额外的数据库调用 - 一个用于检查所有权的db调用(在授权检查期间)和一个用于实际获取实体的db调用(通过执行控制器操作)。

    [Authorize(Policy = "yourOwnershipPolicy")]
    public async Task<IActionResult> GetById(int id)
    {
        var entity = _yourCrudInstance.GetById(id);
    
        return Ok(entity);
    }
    
  3. 我将使用第一个解决方案,直到找到为我的通用CRUD存储库创建通用授权处理的方法,因为可能忘记为新实体创建所需的授权策略,但是不能忘记提供参数ownerId到.GetById(id, ownerGuid),前提是没有重载方法,或者代码没有编译。

    <强>更新

    1. 我找到了第三个解决方案,其中可以创建一种通用授权属性。诀窍是在授权属性中使用具体存储库的类型作为输入参数。然而,仍然存在一个限制:必须为每种类型的Id复制授权属性,例如int Id,Guid id等。但是,这仍然会减少重复代码到id类型。在大多数情况下,人们只有一种类型的id,可能是int或Guid。
    2. 这里有一些代码可以演示我的架构。它被大量总结和编辑,但应该成功编译。我的原始代码正在生产中:

      using System;
      using System.Linq;
      using System.Threading.Tasks;
      using Microsoft.AspNetCore.Authorization;
      using Microsoft.AspNetCore.Mvc;
      using Microsoft.AspNetCore.Mvc.Filters;
      using Microsoft.EntityFrameworkCore;
      using Microsoft.Extensions.DependencyInjection;
      
      [Route("api/yourcontroller")]
      public class YourApiController : Controller
      {
          private readonly YourEntityXYZRepository _repo;
      
          public YourApiController(YourDbContext yourDbContext)
          {
              _repo = new YourEntityXYZRepository(yourDbContext);
          }
      
          [HttpGet("{id}")]
          [AuthorizeOwnerIntId(typeof(YourEntityXYZRepository), Policy = "YourCustomPolicy")]
          public async Task<IActionResult> GetById(int id)
          {
              var entity = _repo.GetById(id);
              return Ok(entity);
          }
      }
      
      // The "generic" authorization attribute for type int id
      // Similar authorization attributes for every type of id must be created additionally, for example Guid
      [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
      public class AuthorizeOwnerIntIdAttribute : AuthorizeAttribute, IAuthorizationFilter
      {
          private object _entityRepositoryObject;
          private IAsyncOwnerIntId _entityRepository;
          private readonly Type _TCrudRepository;
      
          public AuthorizeOwnerIntIdAttribute(Type TCrudRepository)
          {
              _TCrudRepository = TCrudRepository;
          }
      
          public void OnAuthorization(AuthorizationFilterContext context)
          {
              var yourDbContext = context.HttpContext.RequestServices.GetService<YourDbContext>();
              _entityRepositoryObject = Activator.CreateInstance(_TCrudRepository, yourDbContext);
              _entityRepository = _entityRepositoryObject as IAsyncOwnerIntId;
      
              var user = context.HttpContext.User;
      
              if (!user.Identity.IsAuthenticated)
              {
                  // it isn't needed to set unauthorized result 
                  // as the base class already requires the user to be authenticated
                  // this also makes redirect to a login page work properly
                  // context.Result = new UnauthorizedResult();
                  return;
              }
      
              // get entityId from uri
              var idString = context.RouteData.Values["id"].ToString();
              if (!int.TryParse(idString, out var entityId))
              {
                  context.Result = new UnauthorizedResult();
                  return;
              }
      
              // get subjectId from user claims
              var ownerIdString = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
              if (!Guid.TryParse(ownerIdString, out var ownerGuid))
              {
                  context.Result = new UnauthorizedResult();
                  return;
              }
      
              if (!_entityRepository.IsEntityOwner(entityId, ownerGuid))
              {
                  context.Result = new UnauthorizedResult();
              }
          }
      }
      
      // Your concrete repository
      public class YourEntityXYZRepository : AsyncCrud<YourEntityXYZ, int>,
          IAsyncOwnerIntId // Note that type concrete IAsyncOwnerIntId is only implemented in concrete repository
      {
          public YourEntityXYZRepository(YourDbContext yourDbContext) : base(yourDbContext)
          {
      
          }
      }
      
      // Your generic Crud repository
      public abstract class AsyncCrud<TEntity, TId> : IAsyncCrud<TEntity, TId>
          where TEntity : class, IEntityUniqueIdentifier<TId>, IEntityOwner
          where TId : struct
      {
          protected YourDbContext YourDbContext;
      
          public AsyncCrud(YourDbContext yourDbContext)
          {
              YourDbContext = yourDbContext;
          }
      
          // Note that the following single concrete implementation satisfies both interface members 
          // bool IsEntityOwner(TId id, Guid ownerGuid); from IAsyncCrud<TEntity, TId> and
          // bool IsEntityOwner(int id, Guid ownerGuid); from IAsyncOwnerIntId
          public bool IsEntityOwner(TId id, Guid ownerGuid)
          {
              var entity = YourDbContext.Set<TEntity>().Find(id);
              if (entity != null && entity.OwnerGuid == ownerGuid)
              {
                  return true;
              }
      
              return false;
          }
      
          // Further implementations (redacted)
          public Task<bool> SaveContext() { throw new NotImplementedException(); }
          public Task<TEntity> Update(TEntity entity){ throw new NotImplementedException(); }
          public Task<TEntity> Create(TEntity entity, Guid ownerGuid) { throw new NotImplementedException(); }
          public Task<bool> Delete(TId id) { throw new NotImplementedException(); }
          public Task<bool> DoesEntityExist(TId id) { throw new NotImplementedException(); }
          public virtual Task<TEntity> GetById(TId id) { throw new NotImplementedException(); }
      }
      
      // The interface for the Crud operations
      public interface IAsyncCrud<TEntity, TId>
          where TEntity : class, IEntityUniqueIdentifier<TId>
          where TId : struct
      {
          bool IsEntityOwner(TId id, Guid ownerGuid);
          Task<bool> DoesEntityExist(TId id);
          Task<TEntity> GetById(TId id);
          Task<TEntity> Create(TEntity entity, Guid ownerGuid);
          Task<TEntity> Update(TEntity entity);
          Task<bool> Delete(TId id);
          Task<bool> SaveContext();
      }
      
      // The interface for the concrete type method for int id
      // Similar interfaces for every type of id must be created additionally, for example Guid
      public interface IAsyncOwnerIntId
      {
          bool IsEntityOwner(int id, Guid ownerGuid);
      }
      
      // Typical db context
      public class YourDbContext : DbContext
      {
          public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
          {
      
          }
      
          public DbSet<YourEntityXYZ> YourEntityXYZ { get; set; }
      }
      
      
      public class YourEntityXYZ : IEntityUniqueIdentifier<int>, IEntityOwner
      {
          public int Id { get; set; }
          public Guid? OwnerGuid { get; set; }
          // ... Additonal custom properties
      }
      
      public interface IEntityUniqueIdentifier<TId>
          where TId : struct
      {
          TId Id { get; set; }
      }
      
      public interface IEntityOwner
      {
          Guid? OwnerGuid { get; set; }
      }