SQL Server 中的动态SQL 和所有权链

前文一样,我试图使用所有权链,但似乎并不能正常运行。在我的案例中,我在存储过程中创建执行一个拼接SQL(动态SQL),然后使用EXEC 或者 EXEC sp_executesql。然而,当我执行字符串时,我一直收到拒绝访问的错误。两个对象在同一个架构下,每一个对象都没有显示指定拥有者。我哪里做错了呢?

无论什么时候你使用EXEC 或者 EXEC sp_executesql执行SQL 字符串时,SQL Server实际上是在一个单独的批处理中执行的。如果我们查看线上 sp_executesql 使用文档,我们将看到如下解释:

When either sp_executesql or the EXECUTE statement executes a string, the string is executed as its own self-contained batch. SQL Server compiles the Transact-SQL statement or statements in the string into an execution plan that is separate from the execution plan of the batch that contained the sp_executesql or the EXECUTE statement. The following rules apply for self-contained batches:

EXECUTE在线说明文档有进一步的指导说明:

Permissions are not required to run the EXECUTE statement. However, permissions are required on the securables that are referenced within the EXECUTE string. For example, if the string contains an INSERT statement, the caller of the EXECUTE statement must have INSERT permission on the target table. Permissions are checked at the time EXECUTE statement is encountered, even if the EXECUTE statement is included within a module. 

总结一下,当在另外一个模块中使用动态SQL时,如存储过程,动态SQL在一个单独的批处理中执行。结果,所有权链被破坏。实际上,所有批处理相关的函数都被破坏。例如,考虑下面的代码块。我们在动态SQL 外设置一个特定的批处理设置(ARITHABORT 和 ANSI_WARNINGS),并在动态SQL 中改变它们,然后,在动态SQL完成后再进行测试。这来自于联机书籍《  Batch Execution Environment and MARS》主题:

PRINT 'Outside Dynamic SQL Execution:';
SET ARITHABORT ON;
SET ANSI_WARNINGS ON;
PRINT 1/0;
PRINT '---------------------------------';
PRINT 'Inside Dynamic SQL Execution:';
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = 'SET ARITHABORT OFF;
            SET ANSI_WARNINGS OFF;
            PRINT 1/0;
            PRINT ''---------------------------------'';'
EXECUTE sp_executesql @SQL;
PRINT 'Back outside Dynamic SQL Execution:';
PRINT 1/0;

执行上面的语句,我们得到下面的结果:

我们对ARITHABORT 和 ANSI_WARNINGS 的更改仅仅在动态SQL中有效。当其完成后,设置回归到动态SQL执行之前。即使我们在动态SQL完成后没有进行任何设置更改,也会出现这种情况。因为是一个批处理,我们必须有动态SQL中引用对象的权限,我们来看这样一个例子:

USE test;
GO
CREATE USER LimitedUser WITHOUT LOGIN;
GO
CREATE ROLE ExampleRole;
GO
EXEC sys.sp_addrolemember 'ExampleRole', 'LimitedUser';
GO
CREATE TABLE dbo.ATable (TableInt INT);
GO
CREATE PROCEDURE dbo.AProc
AS
BEGIN
   DECLARE @SQL NVARCHAR(MAX);
   SET @SQL = 'SELECT TableInt FROM dbo.ATable';
   EXECUTE sp_executesql @SQL;
END;
GO
GRANT EXECUTE ON dbo.AProc TO ExampleRole;
GO

我们在Limiteduser 上下文中执行:

EXECUTE AS USER = 'LimitedUser';
GO
EXEC dbo.AProc;
GO
REVERT;
GO

当我们执行上面的语句时,返回如下错误:

有两种方案解决这个问题。第一种是简单的赋予合适的权限给引用的对象,如:

GRANT SELECT ON dbo.ATable TO ExampleRole;
GO

我们回过头来在LimitedUser上下文中执行上面的测试,就不会再报错了。这里捕获到的的是,用户现在可以直接访问对象了。例如,我们赋予对dbo.ATable 的SELECT权限。在我们的例子中,这没有什么大不了的。但是如果你对大表赋予INSERT、UPDATE或者DELETE权限,这可能有大问题。

如果您仍然在使用SQL Server 7.0或2000,那么您就卡住了。没有其他解决方案了。然而,如果你使用的是SQL Server 2005 或以上版本,你可以在存储过程的申明中使用EXECUTE AS语句,例如:

REVOKE SELECT ON dbo.ATable TO ExampleRole;
GO
DROP PROCEDURE dbo.AProc;
GO
CREATE PROCEDURE dbo.AProc
WITH EXECUTE AS OWNER
AS
BEGIN
   DECLARE @SQL NVARCHAR(MAX);
   SET @SQL = 'SELECT TableInt FROM dbo.ATable';
   EXECUTE sp_executesql @SQL;
END;
GO

我在上面的语句中包括清理的REVOKE SELECT和DROP PROCEDURE的语句,因此我们可以确保在创建存储过程中的 EXECUTE AS 语句其作用(执行CREATE PROCEDURE 创建是也不会报错)。但是,如果您以LimitedUser的身份,调用并执行,就像显式地授予SELECT权限时那样,那么执行存储过程的调用就会成功。

清除测试内容:

EXEC sys.sp_droprolemember 'ExampleRole', 'LimitedUser';
DROP ROLE ExampleRole
DROP USER LimitedUser
DROP PROC AProc

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章