【问题标题】:Authorize data access at database level在数据库级别授权数据访问
【发布时间】:2014-10-06 20:20:57
【问题描述】:

从问题标题中您可能会猜到这是关于什么的。我将尝试描述我目前拥有的以及我想要归档的内容。

假设一个应用程序处理四个实体:UserTeamRepositoryDocument。这些实体之间的关系是:

  • 每个用户都属于零个或多个团队
  • 每个文档都属于一个存储库
  • 用户可以拥有零个或多个存储库
  • 每个 repository 都可以创建为 publicprivate
  • 公共存储库的内容对所有与存储库所有者共享团队用户可见。
  • 私有存储库仅对其所有者可见。

访问用户的文档不是问题,这些都是存储在他拥有的存储库中的所有文档。但是事情变得复杂了,因为我真正需要的是对用户可见的所有文档,这是所有文档以及其他人公开并与他共享团队的文档。

目前我正在数据访问层中执行此授权机制。这意味着获取所有文档并按照上述规则进行一些过滤。我知道这个实现是不可扩展的,我想知道我是否可以通过将授权逻辑移动到数据库来改进我的数据库模型。这样过滤将由数据库引擎完成,只有请求的实体才会返回给客户端代码。

这个问题与特定的实现无关,但我会为我正在使用的特定工具标记它。也许它对某人的回答有用。

【问题讨论】:

  • 查看您的标签,我想您使用 Linq to 实体来执行查询文档。从您的问题来看,我真的不明白为什么您应该获取所有文档并在代码中过滤它们。你不能在 EF 编译 SQL 查询之前使用 Linq 查询。要使您的用户、团队和(取决于您的确切设计)存储库也必须在数据库中,但您的问题已经指向数据库引擎。
  • @Ric.Net 如果我错了,请纠正我,但我认为选择正确文档集的 LINQ 查询无法由 EF 翻译。它会尽力而为,但仍会检索所有文档。
  • 如果模型正确,EF 将不会获取所有记录。今晚我将添加一个答案。问题:存储库可以由多个用户拥有吗?如果您称其为存储库的“所有者”,那就太奇怪了。
  • @Ric.Net 存储库将归一个用户所有。
  • A 完全基于 Linq 查询添加了答案。我将进一步编辑此答案以使其成为完整的示例。现在去睡觉;-)

标签: c# database-design asp.net-mvc-5 authorization entity-framework-6


【解决方案1】:

首先让我解释一下为什么使用实体框架(或其他 ORM 工具)比使用存储过程更优雅。

Stored Procedures are evil。这就是为什么。正如链接中详细解释的那样,存储过程往往会作为第二个 BL 增长,因此难以维护。当在多个存储过程中使用该列时,重命名列这样的简单任务将成为一项大任务。当您使用 ORM 工具时,Visual Studio 将为您完成大部分工作。

这让我想到了实体框架的第二个优势。您可以使用自己喜欢的 .net 语言编写查询。实体框架不会直接执行您的查询。您可以控制查询何时执行,您可以阅读here。执行此操作时,此实体框架会将您的 Linq 语句编译为完整的 tsql 语句并针对数据库运行它。所以绝对没有必要获取所有数据并循环遍历每条记录。

提示:将光标移到变量名上,ef 将为您提供它将编译的 TSQL 语句的预览。

那么你的 Linq 查询应该是什么样子的呢?我根据您的描述编写了一个测试数据库,并为其制作了一个实体框架(ef6)模型,如下所示:

这个 Linq 查询会做你想做的事,至少我正确理解了你的问题。

private IEnumerable<Document> GetDocumentsOfUser(Guid userId)
    {
        using (var db = new DocumentRepositoryEntities())
        {
            // Get owned repositories by the user
            var ownedRepositories = db.Repositories
                          .Where(r => r.Owner.UserId == userId);

            // Get all users of teams the user belongs to
            var userInOtherTeams =
                 db.Users.Where(u => u.UserId == userId)
                        .SelectMany(u => u.Teams)
                        .SelectMany(t => t.Users);

            // Get the public repositories owned by the teammembers
            var repositoriesOwnedByTeamMembers =
                userInOtherTeams.Where(u => u.Repositories.Any())
                                .SelectMany(u => u.Repositories)
                                .Where(r => !r.Private);

            // Combine (union) the 2 lists of repositories
            var allRepositories = ownedRepositories.Concat(
                                  repositoriesOwnedByTeamMembers);

            // Get all the documents from the selected repositories
            return allRepositories.SelectMany(r => r.Documents)
                                  .Distinct()
                                  .ToArray(); //query will be composed here!
        }
    }

请注意,当调用.ToArray() 时,linq 语句将被编译为 TSQL 选择语句。

【讨论】:

  • 您的回答听起来不错。我会尽快尝试的。期待你的更新。谢谢:)
  • 我想祝贺你这个高质量的答案。我测试了你的答案,它工作正常。它与我实现的非常相似,我认为我担心它不会高效,因为它是表的复杂组合。理想的解决方案可能是使用您在答案中描述的技术在数据访问层和客户端代码之间构建授权层。
  • @agarwaen 谢谢!我认为您可以轻松地将这种方法扩展到单独的身份验证层,使用可以在其中注入某种 IUserContext 的存储库,而不是直接与 DbContext 对话。对于一些想法,看看这个discussion
【解决方案2】:

根据您的描述,目标是找到用户当前有权访问的所有存储库,然后从每个存储库中检索文档。

如果这是我的实现,我会在接受当前用户 ID 的数据库中添加一个存储过程,然后将可访问的存储库列表收集到本地表变量中,然后从文档表中选择文档的存储库位于可访问的存储库列表中。

DECLARE
  @Teams TABLE (TeamID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY (TeamID))

DECLARE
  @Repositories TABLE (RepositoryID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY (RepositoryID))

  /* Get the list of teams the user is a member of */
INSERT INTO @Teams
SELECT Teams.TeamID
  FROM Teams INNER JOIN TeamUsers ON Teams.ID = TeamUsers.TeamID
 WHERE TeamUsers.UserID = @UserID

  /* Get the list of repositories the user shares a team member with */
INSERT INTO @Repositories
SELECT RepositoryID
  FROM Repositories
 WHERE OwnerID = @UserID 
    OR (OwnerID IN (SELECT DISTINCT TeamUsers.UserID
                     FROM TeamUsers INNER JOIN @Teams ON TeamUsers.TeamID = @Teams.TeamID)
       AND IsShared = 1)

  /* Finally, retrieve the documents in the specified repositories */
SELECT Documents.* 
  FROM Documents INNER JOIN @Repositories ON Documents.RepositoryID = @Repositories.RepositoryID

【讨论】:

    【解决方案3】:

    虽然competent_tech 建议的答案是有效的,如果您的需求是一次性的,这很好,但理想情况下,您希望做的是以外部化的方式在专用层中实现您的授权要求。这样做的原因包括:

    • 更易于维护解耦架构
    • 您无需接触您的应用程序和/或数据库即可更新您的授权
    • 您不需要 SQL/存储过程知识
    • 您可以更轻松地报告在何处应用了哪些授权:如果您有审核员在您的脖子上呼吸,这一点很重要。

    要实现外部化授权(有关该主题的Gartner report,请参见此处),您需要考虑基于属性的访问控制 (ABAC - see here for a report on ABAC by NIST) 和可扩展访问控制标记语言(XACML - more info here) 作为实现 ABAC 的一种手段。

    如果你遵循 ABAC 方法,你会得到:

    • 具有以下概念的干净、解耦的架构
      • 将位于您的应用程序和数据库之间的执行点或拦截器(在 ABAC 应用于数据库的情况下)
      • 一个授权决策引擎,它会产生一个过滤语句(在 SQL 数据库的情况下是一个 WHERE 子句),执行点将附加到原始 SQL 语句中
    • 一种基于策略和基于属性的授权模型,您可以使用易于理解的语句而不是过程、PL-SQL 或其他 SQL 工件来编写授权要求。示例包括:
      • *用户可以编辑他们拥有的文档
      • 如果用户的团队 == 文档的团队,则用户可以查看文档
      • 当且仅当文档标记为公开时,用户才能查看其他团队的文档
      • 当且仅当文档状态为草稿时,具有编辑者角色的用户才能编辑属于其团队的文档*

    在上述示例中,用户类型、资源类型(文档)、操作(查看、编辑)、文档的团队、用户的团队以及文档的可见性(私有或公共)都是属性的示例。属性是生命线,是 ABAC 的基石。

    ABAC 可以轻松帮助您实施从最简单到更高级的授权要求(例如可以在出口法规、合规法规或其他业务规则中找到)。

    这种方法的一个好处是它不是特定于数据库的。您可以将相同的原则和策略应用于本土应用程序、API、Web 服务等。这就是我所说的外部授权的任何深度架构/方法。下图很好地概括了它:

    PDP 是您的集中式授权引擎。

    【讨论】:

    • 您的回答听起来很有希望。我同意授权应该有一个专门的层,你提出的确实是我想要实现的。我的项目很小,competent_tech 的答案可能就足够了,但我仍然想了解在一般情况下应该如何处理授权。我仍在寻找具体的实现/示例,因为您提供的链接还不够。
    • 看看我工作的公司 Axiomatics(免责声明)。我们为不同的层/API、Web 应用程序、数据库和 SharePoint 进行外部化授权。 axiomatics.com。我们有很多客户将 authZ 外部化。
    猜你喜欢
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2015-08-02
    • 1970-01-01
    • 2014-11-10
    • 2016-06-15
    • 2012-09-13
    • 2011-01-01
    相关资源
    最近更新 更多