【问题标题】:Storing ASP.NET style security in SQL Server xml column?在 SQL Server xml 列中存储 ASP.NET 样式的安全性?
【发布时间】:2014-12-08 08:17:37
【问题描述】:

我想在 SQL Server 数据库中存储记录的安全信息。出于一致性目的,安全信息最好与您在配置文件中看到的格式相同:

<authorization>
     <allow roles="Admins"/>
     <allow users="SomeGuy,SomeOtherGuy"/>
     <deny users="*"/>
</authorization>

然后,我希望能够在数据库中查询允许特定用户访问的所有内容,给定他们的用户名和角色列表。

有人对如何最好地做到这一点有建议吗?还是我走错了路?

一个简单的蛮力解决方案是只读取数据库中的每一行并将每个安全规则 XML 拉入某个将为我进行评估的类 - 但显然这会很慢并且在大型表上将是不合理的。

想到的另一件事是制作某种子表,其中包括某种优先级,以指示应应用每个允许或拒绝节点的顺序。但是,我有很多表需要这个功能,如果我可以避免创建大量子表,那将是理想的。

虽然我在 SQL Server 中使用 XML 列的经验有限,但我可能可以构建一个 XML 查询来确定是否允许用户 - 也许以 (/authorization/allow/@users)[1] 开头。但是,节点的顺序很重要,所以虽然我可能会找到一个与给定名称或角色匹配的节点,但我不知道如何执行任何基于集合的操作来检查用户是被拒绝还是基于允许哪个是第一位的。

那么,给定用户名和逗号分隔的角色列表,检查该人对数据库中特定行的访问权限的最佳方法是什么?

【问题讨论】:

  • 您是否正在尝试根据角色实施某种滚动您自己的行级安全性?
  • 是的,这就是我想要做的。我希望管理员能够通过简单地添加一个预先指定名称的 XML 列并将安全规则推送到该列中来动态控制对任何表中各种记录的访问。
  • 创建这样的行级安全性是一项艰巨的工作。您可能会很幸运并找到这样做的人,但这是一个比在线论坛更适合的话题。
  • 我知道这是一项艰巨的工作 - 但我只是在问一个相对简单的问题(除非有人想明确告诉我我的做法是错误的)。我有一个要查询的已定义 XML 结构。我可以访问以逗号分隔的角色列表。我想使用基于集合的操作来评估 XML,而不是循环每一行并使用 C# 代码进行。我相信我可以让它工作,但我希望有更多 MSSQL XML 经验的人可以建议一个特定的查询。
  • 您可以覆盖角色提供者并在应用程序层中处理这个问题,这比将它以 XML 形式放入数据库中要容易得多。似乎您以错误的方式进行操作,但是您没有向我们提供足够的信息来确定这一点——这需要您对业务/系统限制的了解程度才能确定。了解您认为需要行级安全性的为什么会很有帮助;因为这将指导任何潜在的解决方案。

标签: c# sql asp.net sql-server xml


【解决方案1】:

嗯,我想出了一个解决方案,但并不理想。对于 10,000 条记录,返回与安全配置文件匹配的所有行需要 5 秒。这不是一场彻头彻尾的灾难,它确实有效,但我必须稍后再回到这个问题来改进它。

这就是我解决它的方法。请记住,我只为此工作了几个小时。

在我真正做任何事情之前,我知道我需要一个函数来比较两个逗号分隔的列表。我需要在列表中包含用户的角色,并查看这些角色中是否有任何角色出现在存储在我的 xml 列中的授权设置中,如原始帖子中所述。为此,我做了两个函数。

第一个函数是常见的使用xml进行字符串拆分的函数:

IF EXISTS (
    SELECT * FROM sysobjects WHERE id = object_id(N'ufnSplitStrings') 
    AND xtype IN (N'FN', N'IF', N'TF')
)
    DROP FUNCTION ufnSplitStrings
GO

CREATE FUNCTION dbo.ufnSplitStrings
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );

建立该函数后,我可以创建另一个函数,然后进行我想要的比较:

IF EXISTS (
    SELECT * FROM sysobjects WHERE id = object_id(N'ufnContainsAny') 
    AND xtype IN (N'FN', N'IF', N'TF')
)
    DROP FUNCTION ufnContainsAny
GO
CREATE FUNCTION dbo.ufnContainsAny(@List1 NVARCHAR(MAX), @List2 NVARCHAR(MAX))
RETURNS int 
AS 
BEGIN

    DECLARE @Ret AS INT = 0

    SELECT @Ret = COUNT(*) FROM dbo.ufnSplitStrings(@List1, ',') x
    JOIN dbo.ufnSplitStrings(@List2, ',') y ON x.Item = y.Item

    RETURN @Ret
END;
GO

最后,我可以使用该函数来组装我的主要 UserIsAuthorized 函数。

IF EXISTS (
    SELECT * FROM sysobjects WHERE id = object_id(N'ufnUserIsAuthorized') 
    AND xtype IN (N'FN', N'IF', N'TF')
)
    DROP FUNCTION ufnUserIsAuthorized
GO
CREATE FUNCTION dbo.ufnUserIsAuthorized(@SecurityRules XML, @UserName NVARCHAR(64), @UserRoles NVARCHAR(MAX))
RETURNS int 
AS 
BEGIN
    DECLARE @ret int = 0;
    DECLARE @AuthType NVARCHAR(32);


    DECLARE @authRules Table (a nvarchar(32), u nvarchar(max), r nvarchar(max), o int)

    INSERT INTO @authRules
    SELECT
        a = value.value('local-name(.[1])', 'varchar(32)'),
        u = ',' + value.value('@users', 'varchar(max)') + ',',
        r = ',' + value.value('@roles', 'varchar(max)') + ',',
        o = value.value('for $i in . return count(../*[. << $i]) + 1', 'int')
    FROM @SecurityRules.nodes('//allow,//deny') AS T(value)

    SELECT TOP 1 @AuthType = a FROM @authRules
    WHERE CHARINDEX(',' + @UserName + ',', u) > 0 OR CHARINDEX(',*,', u) > 0 OR dbo.ufnContainsAny(r, @UserRoles) > 0 OR CHARINDEX(',*,', r) > 0
    GROUP BY a
    ORDER BY MIN(o)

    IF (@AuthType IS NOT NULL AND @AuthType = 'allow')
        SET @ret = 1;

    RETURN @ret;
END;

该函数将 xml 允许和拒绝节点拆分为一个表,其中包含授权类型(允许或拒绝)、用户列表、角色列表,最后是特定节点在文档中出现的顺序。最后,我可以抓取找到用户或用户角色之一的第一个节点。如果该节点是“允许”,那么我返回一个 1。

是的,这有点可怕,因为我们在每个调用中都声明了一个表。我尝试了各种小测试,我只查找用户名(以避免必须对 ufnContainsAny 进行任何调用),但性能没有改变。我还尝试将“o”列更改为简单的标识列,因为我正在选择所有节点 - 这将允许它跳过我认为可能是获取节点顺序的耗时计算。但这也不影响性能。

因此,这种方法需要工作也就不足为奇了。如果有人有任何建议,我会全力以赴。

我最初使用此功能的行数很少,因此我可以在中间使用它,直到我想出更好的解决方案(或完全放弃此方法)。

编辑:

只需跳过 DECLARE 表/INSERT 即可显着提高性能。相反,我们可以这样做:

SELECT TOP 1 @AuthType = a FROM 
(
SELECT
    a = value.value('local-name(.[1])', 'varchar(32)'),
    u = ',' + value.value('@users', 'varchar(max)') + ',',
    r = ',' + value.value('@roles', 'varchar(max)') + ',',
    o = value.value('for $i in . return count(../*[. << $i]) + 1', 'int')
FROM @SecurityRules.nodes('//allow,//deny') AS T(value)
) AS sec
WHERE CHARINDEX(',' + @UserName + ',', u) > 0 OR CHARINDEX(',*,', u) > 0 OR dbo.ufnContainsAny(r, @UserRoles) > 0 OR CHARINDEX(',*,', r) > 0
GROUP BY a
ORDER BY MIN(o)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-07
    • 1970-01-01
    • 2012-11-18
    • 2020-11-18
    • 2011-03-21
    • 2013-05-02
    • 2012-08-09
    • 2011-09-06
    相关资源
    最近更新 更多