如何在Entity Framework ASP.NET Core应用程序上应用自定义级联安全。

huangapple go评论75阅读模式
英文:

How to apply custom cascading security on Entity Framework ASP.NET Core application

问题

设置场景

我们有一个使用Entity Framework代码优先的ASP .NET Core应用程序。

在我们的应用程序中,有许多不同的关系对象,具有用户可自定义的数据访问权限。这些权限包括用户允许/拒绝规则和组允许/拒绝规则(组是用户的集合)。

我们还有一些继承线正在发生。例如,可能有一个Town对象,一个Street对象,一个House对象和一个Room对象。权限(用户/组允许/拒绝)可以针对这些对象中的每一个进行设置。

  • TownStreets作为导航属性
  • StreetHouses作为导航属性
  • HouseRooms作为导航属性

只有在满足Room本身、父House、父Street和父Town上设置的允许/拒绝规则时,才能访问Room

在考虑性能问题的情况下,如何在Entity Framework中配置这个的最佳方式是什么?

当前配置

目前,在我们的DbContext中有一个自定义过滤表达式,检查正在访问的对象:

// 如果用户在用户或组允许列表上并且不是被拒绝的用户或组,则允许访问
entityExpression = t =>
    ((((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.UserId == AbpSession.UserId && security.PermissionState == PermissionState.Allow) ||
    ((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.Group.Members.Any(p => p.UserId == AbpSession.UserId) && security.PermissionState == PermissionState.Allow)) &&
    !((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.UserId == AbpSession.UserId && security.PermissionState == PermissionState.Deny) &&
    !((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.Group.Members.Any(p => p.UserId == AbpSession.UserId) && security.PermissionState == PermissionState.Deny)) ||
    ((IFullAuditedEntityWithSecurity)t).AllowPublicAccess;

在检查继承线时,安全性问题开始出现,例如:

// 如果是Street,则检查父Town的安全性
if (typeof(IStreet).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> parentSecurity = t => ((IStreet)t).Town != null;
    parentSecurity = isBeingAdded != null ? parentSecurity.Or(isBeingAdded) : parentSecurity;
    expression = expression == null ? parentSecurity : CombineExpressions(expression, parentSecurity);
}

// 如果是House,则检查父Street的安全性
if (typeof(IHouse).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> parentSecurity = t => ((IHouse)t).Street != null;
    parentSecurity = isBeingAdded != null ? parentSecurity.Or(isBeingAdded) : parentSecurity;
    expression = expression == null ? parentSecurity : CombineExpressions(expression, parentSecurity);
}

// 如果是Room item,则检查父House的安全性
if (typeof(IRoom).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> parentSecurity = t => ((IRoom)t).House != null;
    parentSecurity = isBeingAdded != null ? parentSecurity.Or(isBeingAdded) : parentSecurity;
    expression = expression == null ? parentSecurity : CombineExpressions(expression, parentSecurity);
}

我们还有一些额外的过滤器,以便用户如果已经可以访问父对象,则可以自动访问子元素。例如,如果用户是一个town的成员,他们将自动访问任何子street:

if (typeof(IStreet).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> allowTownMembersExpression = t =>
        ((IStreet)t).Town.Members.Any(townMember => townMember.UserId == AbpSession.UserId && townMember.StartDate <= DateTime.Now && (townMember.EndDate >= DateTime.Now || townMember.EndDate == null)) &&
        !((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.Group.Members.Any(p => p.UserId == AbpSession.UserId) && security.PermissionState == PermissionState.Deny) &&
        !((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.UserId == AbpSession.UserId && security.PermissionState == PermissionState.Deny);

    entityExpression = entityExpression == null ? allowTeamMembersExpression : entityExpression.Or(allowTeamMembersExpression);
}

这一切都运行良好,除了性能不是最佳的事实。使用分析工具,我们可以看到每当尝试访问任何对象时生成的脚本都非常庞大。对所有父对象的安全性检查以及获取用户、组、组成员的每个父对象都导致脚本太慢而且太大。

考虑到这是我们的业务案例,我们需要执行这个继承安全系统,是否有其他方式可以解决这个问题,既能满足业务逻辑又能减轻每次访问任何对象时对数据库的负担?

提前感谢。

英文:

Setting the scene

We have an ASP .NET Core application using Entity Framework code-first.

In our application, we have a number of different relational objects with user customisable data access permissions. These permissions consist of user allow/deny rules and group allow/deny rules (groups being a collection of users).

We also have a number of inheritance lines occurring. As an example, there may be a Town object, a Street object, a House object and a Room object. Permissions (user/group allow/deny) can be set against each of these objects.

  • Town has Streets as a navigation property
  • Street has Houses as a navigation property
  • House has Rooms as a navigation property

You cannot access a Room unless you satisfy the allow/deny rules set on the Room itself, the parent House, the parent Street and the parent Town.

What is the best way to configure this in Entity Framework keeping in mind performance issues?

Current Configuration

We currently have a custom filter expression in our DbContext which checks whatever object is being accessed:

// Allow access if user on user or group allow list and not a denied user or group
entityExpression = t =>
            ((((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.UserId == AbpSession.UserId && security.PermissionState == PermissionState.Allow) ||
            ((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.Group.Members.Any(p => p.UserId == AbpSession.UserId) && security.PermissionState == PermissionState.Allow)) &&
            !((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.UserId == AbpSession.UserId && security.PermissionState == PermissionState.Deny) &&
            !((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.Group.Members.Any(p => p.UserId == AbpSession.UserId) && security.PermissionState == PermissionState.Deny)) ||
            ((IFullAuditedEntityWithSecurity)t).AllowPublicAccess;

Problems with security start to occur when checking the inheritance line, for example:

// If street, check parent town security
if (typeof(IStreet).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> parentSecurity = t => ((IStreet)t).Town != null;
    parentSecurity = isBeingAdded != null ? parentSecurity.Or(isBeingAdded) : parentSecurity;
    expression = expression == null ? parentSecurity : CombineExpressions(expression, parentSecurity);
}

// If house, check parent street security
if (typeof(IHouse).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> parentSecurity = t => ((IHouse)t).Street != null;
    parentSecurity = isBeingAdded != null ? parentSecurity.Or(isBeingAdded) : parentSecurity;
    expression = expression == null ? parentSecurity : CombineExpressions(expression, parentSecurity);
}

// If room item, check parent house security
if (typeof(IRoom).IsAssignableFrom(typeof(TEntity)))
{
    Expression<Func<TEntity, bool>> parentSecurity = t => ((IRoom)t).House != null;
    parentSecurity = isBeingAdded != null ? parentSecurity.Or(isBeingAdded) : parentSecurity;
    expression = expression == null ? parentSecurity : CombineExpressions(expression, parentSecurity);
}

We also have some additional filters so that users can get auto access to child elements if they already have access to parent objects. For example, if a user is a town member they get auto access to any child streets:

if (typeof(IStreet).IsAssignableFrom(typeof(TEntity)))
{
	Expression<Func<TEntity, bool>> allowTownMembersExpression = t =>
				((IStreet)t).Town.Members.Any(townMember => townMember.UserId == AbpSession.UserId && townMember.StartDate <= DateTime.Now && (townMember.EndDate >= DateTime.Now || townMember.EndDate == null)) &&
				!((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.Group.Members.Any(p => p.UserId == AbpSession.UserId) && security.PermissionState == PermissionState.Deny) &&
				!((IFullAuditedEntityWithSecurity)t).Permissions.Any(security => security.UserId == AbpSession.UserId && security.PermissionState == PermissionState.Deny);

	entityExpression = entityExpression == null ? allowTeamMembersExpression : entityExpression.Or(allowTeamMembersExpression);
}

This is all working nicely, except for the fact the performance is not the greatest. Using profiler we can see that the scripts generated any time we try to access any object are huge to say the least. The security checks on all the parent objects and the subsequent get of users, groups, group members foreach one of those parent objects in resulting in scripts that are way too slow and way too big.

Considering that this is our business case and we need to enforce this inheritance security system, is there any other way of going around it that would still satisfy the business logic but be less of a load on the database every time we access any object?

Thank you in advance

答案1

得分: 0

**注意:关于关系型数据库的问题在于关系的深度。我们通过减少这些关系来获得最佳结果。

我有一系列的对象,每个对象都有一组权限,通过这些权限,我们的世界中的角色能够访问特定对象。

在我的观点中,我将其分为两个阶段的权限验证:1. 提交2. 评估

1. 提交阶段:
在这个阶段,我要编辑这些权限(我假设权限编辑是一个低重复任务,相对于读取权限而言,这是一个关键点)。在这个阶段,我将尽可能多地应用开销。我将检查权限继承,级联权限评估,条件权限检查以及由我的应用程序要求强制执行的所有其他情况。
故事的结尾,我只是存储类似这样的东西:Alice在ObjectY上有权限x,也许是这样的:

ObjectY.PermissionSet.Add(new PermissionGrant() 
{ 
    Type = "Walking", 
    Actor = "Alice", 
    Access = "Granted" 
});

在这个实现中,我只有1个关系。MyWorldObjectPermissionSet。这对性能提升很大。

2. 评估阶段:
在这个阶段,我只想检查Alice是否在街道Y上具有权限X。这是一个简单的任务,开销很低。我只需获取街道对象,然后在那里查找Alice

我仍然对这个实现不满意,因为我的Actors数量不断增长,我必须在PermissionGrant表上存储大量记录(在我的示例中)。我将尝试在这个表上使用索引,也许是复合索引。了解更多关于索引的信息

如果你仍然对这个实现不满意,可以考虑使用缓存方案。

这个实现的主要问题是更新权限和权限推理的不足。

  • 您可以根据AcccessUpdate操作的发生来降低更新成本。要降低这个成本,您应该在提交评估阶段之间平衡这些检查。
  • 至于权限推理,有人可能会问**当我手动更新数据库时会发生什么?**整个应用程序会崩溃。这可能会发生,我们对非法访问数据库不负责。可能会有大量非法访问数据库导致的应用程序崩溃。
英文:

** Note: The problem with relational database is the depth of relations. We get the best where we reduce these relations.

I have a world of objects and each object has a set of permissions and through this permissions actors of our world are able to access to specific object.

In my point of view I break it to two phase validation on Permissions: 1.Submission and 2.Evaluation

1.Submission Phase:
In this phase a I'm about to edit these permissions (I assumed that permission editing is a low repetition task vs reading permissions, That's a key point). In this phase I will apply the most overheads possible. I will check permission inheritances, cascading permission evaluating, conditional permission checking and the all other situations that are imposed by my application requirements.
At the end of the story I just store something like this Alice has permission x on Object y, maybe something like this:

 ObjectY.PermissionSet.Add(new PermissionGrant() 
 { 
     Type = "Walking", 
     Actor = "Alice", 
     Access = "Granted" 
 }); 

I have only 1 relation in this implementation. MyWorldObject to PermissionSet. Thats a lots of performance enhancement.

2.Evaluation Phase:
In this phase I just want to check whether Alice has permission X on street Y or not. Thats a simple task, low overhead. I just fetch the street object and I will looking for Alice there.

I'm not satisfied with this implementation still, because I have a growing number of Actors and I have to store a lots of records on PermissionGrant table (in my example). I will go through using indexes on this table. maybe composite indexes. (Read more on indexes.)

If you are still not satisfied with this implementation you should go for caching scenarios.

Main issue with this implementation is updating permissions and lake of permission inferences.

  • You can lower updating cost based on the occurrence of Acccess and Update operations. to lower this cost you should balance these checkings between Submission and Evaluation phase.

  • About permission inference, someone might say What happens when I update the database manually? whole application crashes. That may happens we are not responsible for illegal access to database. There might be a tons application crash caused by illegal access to our database.

huangapple
  • 本文由 发表于 2023年3月31日 23:59:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75900556.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定