
开发多用户系统时,几乎总是出现限制访问数据的问题。 主要方案如下:
- 认证用户的数据访问限制
- 限制访问经过身份验证但不具备用户必要特权的数据
- 通过直接调用API防止未经授权的访问
- 过滤搜索查询中的数据并列出UI元素(表,列表)
- 防止其他用户更改属于一个用户的数据
对场景1-3进行了很好的描述,通常使用内置的框架工具(例如,
基于角色的授权或
基于声明的授权)来解决。 但是,授权用户可以直接访问“邻居”的数据或在其帐户中执行操作的情况始终存在。 发生这种情况的最常见原因是程序员忘记添加必要的检查。 您可以依靠代码审查,也可以通过应用全局数据过滤规则来防止此类情况。 它们将在本文中讨论。
列表和表格
在ASP.NET MVC中用于接收数据的典型控制器可能看起来
像这样 :
[HttpGet] public virtual IActionResult Get([FromQuery]T parameter) { var total = _dbContext .Set<TEntity>() .Where() .Count(); var items= _dbContext .Set<TEntity>() .Where() .ProjectTo<TDto>() .Skip(parameter.Skip) .Take(parameter.Take) .ToList(); return Ok(new {items, total}); }
在这种情况下,所有过滤数据的责任仅在于程序员。 他会记得有必要在
Where
添加条件吗?
您可以使用
全局过滤器解决问题。 但是,为了限制访问,我们需要有关当前用户的信息,这意味着
DbContext
的构造必须很复杂才能初始化特定字段。
如果规则很多,那么
DbContext
的实现将不可避免地要学习“太多”,这将违反
唯一责任原则 。
粉扑结构
发生数据访问和复制粘贴问题,因为在该示例中,我们忽略了将其划分为几层的问题,并且从控制器中绕过了业务逻辑层,我们立即到达了数据访问层。 这种方法甚至被称为“
笨拙的笨拙的控制器” 。 在本文中,我不想涉及与存储库,服务和业务逻辑结构有关的问题。 全局过滤器可以很好地做到这一点,您只需要将它们应用于另一层的抽象即可。
添加抽象
.NET已经具有用于访问数据的
IQueryable
。
DbContext
直接访问替换为对此类提供者的访问:
public interface IQueryableProvider { IQueryable<T> Query<T>() where T: class; IQueryable Query(Type type); }
为了访问数据,我们将创建以下过滤器:
public interface IPermissionFilter<T> { IQueryable<T> GetPermitted(IQueryable<T> queryable); }
我们以这种方式实现提供程序,使其搜索所有声明的过滤器并自动应用它们:
public class QueryableProvider: IQueryableProvider {
示例中用于获取和创建过滤器的代码不是最佳的。 而不是
Activator.CreateInstance
最好使用已
编译的Expression Trees 。 一些IOC容器支持
开放式仿制药的注册 。 我将把优化问题放在本文的范围之外。
我们实现过滤器
过滤器实现可能如下所示:
public class EntityPermissionFilter: PermissionFilter<Entity> { public EntityPermissionFilter(DbContext dbContext, IIdentity identity) : base(dbContext, identity) { } public override IQueryable<Practice> GetPermitted( IQueryable<Practice> queryable) { return DbContext .Set<Practice>() .WhereIf(User.OrganizationType == OrganizationType.Client, x => x.Manager.OrganizationId == User.OrganizationId) .WhereIf(User.OrganizationType == OrganizationType.StaffingAgency, x => x.Partners .Select(y => y.OrganizationId) .Contains(User.OrganizationId)); } }
我们更正控制器代码
[HttpGet] public virtual IActionResult Get([FromQuery]T parameter) { var total = QueryableProvider .Query<TEntity>() .Where() .Count(); var items = QueryableProvider .Query<TEntity>() .Where() .ProjectTo<TDto>() .Skip(parameter.Skip) .Take(parameter.Take) .ToList(); return Ok(new {items, total}); }
根本没有太多更改。 仍然禁止从控制器直接访问
DbContext
,并且如果正确编写了过滤器,则可以认为数据访问已关闭。 过滤器非常小,因此用测试覆盖它们并不困难。 另外,这些相同的过滤器可用于编写授权代码,以防止未经授权访问“外部”数据。 我将这个问题留在下一篇文章中。