Authorization 具有处理程序的通用授权

Authorization 具有处理程序的通用授权,authorization,asp.net-core-2.0,Authorization,Asp.net Core 2.0,我正在尝试在我的ASP.NET Core 2.0 Web应用程序中实现授权 该应用程序有20个型号,每个型号都有一个控制器,至少实现一个CRUD。我发现了这些,我喜欢使用处理程序来授权请购单的想法。我首先希望实现用户授权,即用户只有查看/编辑自己实体的权限。我的所有数据库实体都有一个OwnerId字段 我发现这些示例似乎只适用于一个特定的控制器 [HttpGet("{id}"] public async Task<IActionResult> GetById(int id) {

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

该应用程序有20个型号,每个型号都有一个控制器,至少实现一个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);
}

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

您是否找到了一个解决方案或解决方案,可以使用授权处理程序或授权属性?我的设置和你的完全一样

我试图创建一个通用属性来服务于所有可能的实体CRUD所有者检查,但设计不允许使用通用属性

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

  • 在控制器操作中,从用户处获取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);
    }
    
  • 我一直使用第一个解决方案,直到找到了为我的通用CRUD存储库创建通用授权处理的方法,因为人们可能会忘记为新实体创建所需的授权策略,但不能忘记向
    提供参数ownerId。GetById(id,ownerGuid)
    ,前提是没有重载方法,或者代码没有编译

    更新:

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

    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; }
    }
    
    使用系统;
    使用System.Linq;
    使用System.Threading.Tasks;
    使用Microsoft.AspNetCore.Authorization;
    使用Microsoft.AspNetCore.Mvc;
    使用Microsoft.AspNetCore.Mvc.Filters;
    使用Microsoft.EntityFrameworkCore;
    使用Microsoft.Extensions.DependencyInjection;
    [路由(“api/yourcontroller”)]
    公共类YourApicController:控制器
    {
    私人只读YourEntityXYZRepository\u repo;
    公共YourApicController(YourDbContext YourDbContext)
    {
    _repo=新的YourEntityXYZRepository(yourDbContext);
    }
    [HttpGet(“{id}”)]
    [AuthorizeOwnerIntId(typeof(YourEntityXYZRepository),Policy=“YourCustomPolicy”)]
    公共异步任务GetById(int id)
    {
    var实体=_repo.GetById(id);
    返回Ok(实体);
    }
    }
    //int-id类型的“通用”授权属性
    //此外,还必须为每种类型的id创建类似的授权属性,例如Guid
    [AttributeUsage(AttributeTargets.Method,AllowMultiple=true,Inherited=true)]
    公共类AuthorizeOwnerIntIdAttribute:AuthorizeAttribute,IAAuthorizationFilter
    {
    私有对象_entityRepositoryObject;
    私人IAsyncOwnerIntId_entityRepository;
    私有只读类型\u tcrud存储库;
    public AuthorizeOwnerIntIdAttribute(类型TCrudRepository)
    {
    _TCrudRepository=TCrudRepository;
    }
    授权时的公共无效(AuthorizationFilterContext上下文)
    {
    var yourDbContext=context.HttpContext.RequestServices.GetService();
    _entityRepositoryObject=Activator.CreateInstance(\u TCrudRepository,yourDbContext);
    _entityRepository=\u entityRepository对象作为IAsyncOwnerIntId;
    var user=context.HttpContext.user;
    如果(!user.Identity.IsAuthenticated)
    {
    //不需要设置未经授权的结果
    //因为基类已经要求对用户进行身份验证
    //这也使得重定向到登录页面正常工作
    //context.Result=新的UnauthorizedResult();
    返回;
    }
    //从uri获取entityId
    var idString=context.RouteData.Values[“id”].ToString();
    如果(!int.TryParse(idString,out var entityId))
    {
    context.Result=新的UnauthorizedResult();
    返回;
    }
    //从用户声明中获取主体
    var ownerIdString=context.HttpContext.User.Claims.FirstOrDefault(c=>c.Type==“sub”)?.Value;
    if(!Guid.TryParse(owneristring,out var ownerGuid))
    {
    context.Result=新的UnauthorizedResult();
    返回;
    }
    if(!\u entityRepository.IsEntityOwner(entityId,ownerGuid))
    {
    context.Result=新的UnauthorizedResult();
    }
    }
    }
    //你的混凝土仓库
    公共类YourEntityXYZRepository:AsyncCrud,
    IAsyncOwnerIntId//注意,类型concrete IAsyncOwnerIntId仅在concrete存储库中实现
    {
    公共YourEntityXYZRepository(YourDbContext YourDbContext):基(YourDbContext)
    {
    }
    }
    //您的通用Crud存储库
    公共抽象类AsyncCrud:IAsyncCrud
    其中tenty:类、IEntityUniqueIdentifier、IEntityOwner
    其中TId:struct
    {
    保护YourDbContext YourDbContext;
    公共异步CRUD(YourDbContext YourDbContext)
    {
    YourDbContext=YourDbContext;
    }
    //请注意,以下单个具体实现满足两个接口成员
    //bool IsEntityOwner(TId id,Guid ownerGuid);来自IAsyncrud和
    //bool IsEntityOwner(int-id,Guid-ownerGuid);来自IAsyncOwnerIntId
    公共bool iEntityOwner(TId id、Guid ownerGuid)
    {
    var entity=YourDbContext.Set().Find(id)