在SQL Server xml列中存储ASP.NET样式安全性?

时间:2014-10-13 17:26:13

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

我想存储SQL Server数据库中记录的安全信息。理想情况下,安全信息的形式与您在配置文件中看到的形式相同,以保持一致性:

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

然后,我希望能够在数据库中查询特定用户可以访问的所有内容,并提供其用户名和角色列表。

是否有人建议如何做到最好?或者我是以错误的方式解决这个问题?

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

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

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

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

1 个答案:

答案 0 :(得分: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 allow和deny节点拆分为一个表,该表包含授权类型(允许或拒绝),用户列表,角色列表,最后是特定节点在文档中出现的顺序。最后,我可以抓住我找到用户的第一个节点或用户的一个角色。如果该节点是&#34;允许&#34;,那么我返回1。

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

因此,毫不奇怪这种方法需要工作。如果有人有任何建议,我全心全意。

我对此功能的初步使用将是非常少的行,所以我可以在此期间使用它,直到我想出一个更好的解决方案(或完全放弃这个方法)。

修改

只需跳过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)