您是否找到了适用于授权处理程序或授权属性的解决方案或解决方法?我的设置和你一模一样。
我试图创建一个通用属性来服务于所有可能的 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 存储库添加一个方法,例如 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);
}
我将使用第一个解决方案,直到找到一种方法为我的通用 CRUD 存储库创建通用授权处理,因为人们可能会忘记为新实体创建所需的授权策略,但不能忘记提供参数 ownerId到.GetById(id, ownerGuid),前提是没有重载方法,或者代码无法编译。
更新:
- 我找到了第三种解决方案,它能够创建一种通用授权属性。诀窍是使用具体存储库的类型作为授权属性中的输入参数。然而,仍然存在一个限制:必须为每种类型的 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; }
}