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

 

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