[英]Authorize data access at database level
从问题标题中,您可能会猜到这是什么意思。 我将尝试描述当前拥有的内容以及要存档的内容。
假设一个应用程序处理四个实体: User , Team , Repository和Document 。 这些实体之间的关系是:
访问用户的文档不是问题,这些都是存储在他拥有的存储库中的所有文档。 但是事情变得复杂了,因为我真正需要的是用户可见的所有文档,这就是所有文档以及其他人公开并与他共享团队的文档。
目前,我正在数据访问层中强制执行此授权机制。 这意味着要获取所有文档并按照上述规则进行一些过滤。 我知道此实现无法扩展,并且我想知道是否可以通过将授权逻辑移至数据库来改善数据库模型。 这样,过滤将由数据库引擎完成,并且只有请求的实体将返回给客户端代码。
这个问题与特定的实现无关,但我将其标记为正在使用的特定工具。 也许对某人的回答很有用。
首先,让我解释一下为什么使用实体框架(或另一个ORM工具)比使用存储过程更优雅。
存储过程是邪恶的 。 这就是为什么。 正如该链接详细解释的那样,存储过程倾向于作为第二个BL增长,因此难以维护。 当该列在多个存储过程中使用时,重命名列这一简单任务将成为一项艰巨的任务。 当您使用ORM工具时,Visual Studio将为您完成大部分工作。
话虽如此,但我却获得了实体框架的第二个优势。 您可以使用自己喜欢的.net语言来组成查询。 实体框架不会直接执行查询。 您可以在此处控制查询何时执行查询。 执行此实体框架时,会将您的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 select语句。
根据您的描述,目标是找到用户当前有权访问的所有存储库,然后从每个存储库中检索文档。
如果这是我的实现,则需要向数据库中添加一个接受当前用户ID的存储过程,然后将可访问存储库列表收集到本地表变量中,然后从documents表中选择文档存储库所在的位置。可访问的存储库列表。
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
主管技术建议的答案是有效的,如果您的需求是一次性的,那么这很好,但理想情况下,您想要做的是在专用层中以外部化方式实现授权要求。 这样做的原因包括:
要实现外部授权 (有关此主题的Gartner报告 ,请参见此处),您需要考虑基于属性的访问控制(ABAC- 有关NIST关于ABAC的报告,请参见此处 )和可扩展访问控制标记语言(XACML)- 更多信息此处 )作为实施ABAC的一种方式。
如果遵循ABAC方法,您将获得:
在上述示例中,用户类型,资源类型(文档),操作(查看,编辑),文档团队,用户团队和文档可见性(私有或公共)都是属性的示例。 属性是生命线,ABAC的基础。
ABAC可以轻松地帮助您实现从最简单的要求到更高级的授权要求(例如可以在出口法规,合规性法规或其他业务规则中找到)。
这种方法的一个好处是它并不特定于数据库。 您可以将相同的原理和策略应用于本地开发的应用程序,API,Web服务等。 这就是我所谓的外部授权的任何深度架构/方法。 下图很好地总结了它:
PDP是您的集中式授权引擎。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.