常規的權限控制,是通過授予和拒絕(Grant/Deny)命令,控制用戶對數據庫對象(數據表或視圖)的訪問權限,用戶訪問的粒度是對象的全部數據行,這意味着,用戶要么有權限訪問該對象,要么沒有權限訪問該對象,無法實現使特定的數據行只允許特定的用戶訪問,但是,行級安全(Row-Level Security,簡稱 RLS)可以實現該該需求。
RLS根據用戶的安全上下文控制用戶可以訪問的數據行。RLS主要由兩部分構成:安全策略(Security Policy)和安全斷言(Security Predicate),一個安全策略可以添加多個安全斷言。
- 如果安全策略(Security Policy)被禁用,那么用戶總是訪問所有數據行,跟數據表上不關聯任何安全策略一樣。
- 如果安全策略(Security Policy)被啟用,那么用戶在數據行級別上控制用戶的訪問,粒度是數據行,控制用戶只能訪問數據表的特定數據行。
RLS是基於安全斷言(Security Predicate)來實現行級的訪問控制,斷言(Predicate )是邏輯表達式,返回的結果是布爾(boolean)值:true 或false。Security Predicate是由內聯表值函數實現的,該函數稱作斷言函數(Predicate Function)。當斷言函數返回的數據行不是空時,安全斷言的結果是True;當斷言函數不返回數據行時,安全斷言的結果是False。
在目標表上實現RLS,控制用戶訪問數據行的權限,通常需要顯式定義3個組件:
- 斷言函數(Predicate Function):是內聯表值函數,用於執行安全斷言,Security Policy調用該函數過濾數據行或阻塞寫操作;
- 安全策略(Security Policy):將目標數據表和斷言函數綁定,並設置安全斷言的類型;
- 權限配置表:如果需要對用戶訪問目標數據表的權限進行動態控制,還需要定義權限配置表。權限配置表通常由兩列:User Name 和 Rowset,用於表示用戶有權限訪問的行集。
一,定義安全斷言(Security Predicate)
如果在數據表上啟用RLS,那么一個用戶訪問數據行的權限受到安全斷言(Security Predicate)的限制,Security Predicate 是在內聯表值函數中定義的邏輯表達式,Security Policy調用內聯表值函數,返回Security Predicate 的結果。在用戶訪問行級別數據時,SQL Server自動執行預定義的安全策略(Security Policy),僅當Security Predicate返回邏輯True時,才允許用戶訪問指定的數據行;如果Security Predicate 返回邏輯False,那么不允許用戶訪問數據。如果在一個數據表上創建了Security Policy,但是,安全策略(Security Policy)被禁用,那么,Security Predicate將不會過濾或阻塞任何數據行,不執行任何的Filter 或 Block操作,用戶能夠訪問所有的數據行。
下面的示例代碼定義了斷言函數(Predicate Function),該表達式根據用戶名作為斷言控制用戶對數據行的訪問:
CREATE FUNCTION rls.fn_securitypredicate (@SalesRep AS sysname) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS fn_securitypredicate_result WHERE @SalesRep = USER_NAME() OR USER_NAME() = 'Manager';
通常情況下,實現RLS的目的是為了根據權限配置表對用戶的訪問目標表的權限進行動態控制。假定權限配置表只有兩列:UserName和Rowset,在定義斷言函數時,需要同時傳遞用戶名稱和RowSet參數。
create function rls.fn_SecurityPredicate_ByUserName (@UserName as sysname,@Rowset as int) returns table with schemabinding as return ( select UserName from rls.UserSecurityConfig where UserName=substring(@UserName, charindex('\',@UserName)+1,len(@UserName)) and RowSet=@Rowset ) go
二,過濾斷言和阻塞斷言(Filter 和 Block)
在Security Policy中,RLS支持兩種類型的安全斷言(Security Predicates):
- Filter Predicate:當用戶從目標表讀取數據行時,Filter Predicate透明地過濾數據行,用戶只能讀取有權限訪問的數據行;如果所有的數據行都被過濾掉,那么返回空集給用戶;
- Block Predicate:當違反斷言時,阻塞寫操作事務的提交,回滾寫操作事務;
1,過濾斷言(Filter Predicate)
當從目標標中讀取數據時,讀操作受到Filter Predicate的影響,讀取數據的操作包括:select,delete和update,用戶不能查詢,刪除和更新被過濾的數據行。
過濾斷言(Filter Predicate)定義的安全策略,使得用戶在Base Table上執行select,update和delete命令時,不會意識到Filter操作的存在,Security Policy透明地過濾數據行,但是用戶能夠插入任何數據,不管數據是否被過濾掉,用戶也可以把數據更新為被過濾的數據。也就是說,用戶更新的數據可能會違反安全斷言。
2,阻塞斷言(Block Predicate)
阻塞斷言(Block Predicate)阻塞所有違反安全斷言的寫操作,有四種阻塞操作:
- After Insert 斷言:阻止用戶插入違反斷言的字段值,就是說,用戶插入的數據必須滿足斷言;
- After Update 斷言:阻止用戶將數據更新為違反斷言的字段值,就是說,數據被更新后,其值必須滿足斷言;
- Before Update 斷言:只允許用戶更新符合斷言的數據行,就是說,在Update之前,對於符合斷言的數據行,用戶能夠更新為任意值;
- Before Delete 斷言:只允許用戶刪除符合斷言的數據行,就是說,在Delete之前,對於符合斷言的數據行,用戶能夠刪除;
阻塞操作有分為After 和Before選項:
- After 指定:在執行Insert 或 Update操作之后,計算斷言的邏輯結果;如果邏輯結果為false,那么回滾Insert 或 Update操作;
- Before 指定:在執行Update 或Delete 操作之前,計算斷言的邏輯結果,用戶只能Update或Delete符合斷言的數據;
- 如果沒有指定,那么默認會指定所有四種阻塞操作。
3,過濾斷言和阻塞斷言的SCHEMABINDING選項
在定義斷言函數時,可能需要與表、視圖進行連接,或者調用函數。當創建Security Policy指定SCHEMABINDING = ON選項,不會對斷言函數內部調用的表或函數做額外的權限檢查。如果指定的SCHEMABINDING = OFF選項,那么用戶需要被授予斷言函數內部調用的表或函數上的SELECT 或EXECUTE權限。也就是說,如果使用SCHEMABINDING = OFF選項創建安全策略,那么在查詢目標表之前之前,必須對斷言函數、以及斷言函數調用的表、視圖或函數具有SELECT 或EXECUTE權限。如果使用SCHEMABINDING = ON選項創建安全策略,當用戶查詢目標表時,將繞過這些權限檢查。
4,安全策略是安全斷言的容器
一個安全策略可以定義多個安全斷言,對多個目標表進行RLS權限控制。
三,創建RLS的示例
使用RLS控制用戶只能訪問指定的數據,第一步是創建斷言函數,第二步是創建安全策略,這兩個對象都有架構。強烈推薦創建一個單獨的Schema,用於RLS對象(Predicate Function和 Security Policy),本例中創建rls架構。
create schema rls authorization dbo;
1,創建斷言函數
創建內聯表值函數,用於過濾數據行,返回安全斷言的結果:
--create function CREATE FUNCTION rls.fn_securitypredicate
(@SalesRep AS sysname) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS fn_securitypredicate_result WHERE @SalesRep = USER_NAME() OR USER_NAME() = 'Manager';
2,創建和啟動安全策略(Security Policy)
創建Security Policy,把目標表和Security Predicate 綁定,添加Filter Predicate,並向斷言函數傳遞目標表 dbo.Sales的字段SalesRep字段作為參數值:
CREATE SECURITY POLICY rls.SalesFilter ADD FILTER PREDICATE rls.fn_securitypredicate(SalesRep) ON dbo.Sales WITH (STATE = ON, SCHEMABINDING=ON);
3,測試安全策略(Security Policy)
由於斷言函數中使用User_Name來獲取用戶的名稱,因此,可以使用EXECUTE AS命令來模擬用戶的權限:
EXECUTE AS USER = 'Sales1'; SELECT USER_NAME() as UserName,* FROM dbo.Sales; REVERT; EXECUTE AS USER = 'Manager'; SELECT USER_NAME() as UserName,* FROM dbo.Sales; REVERT;
如果要避免權限模擬對數據安全帶來的隱患,建議在斷言函數中使用原始Login:
ORIGINAL_LOGIN( )
四,維護安全策略(Security Policy)
安全策略(Security Policy)適用於所有的用戶,包括最高權限角色 sysadmin 和 db_owner 的成員,以及dbo用戶,雖然這些成員擁有很高的權限,能夠更改Security Policy的定義,甚至刪除Security Policy,但是,在訪問數據行時,仍然會受到Security Policy的影響,訪問的數據是Filter 或Block的結果。一個User要想訪問所有的數據行,必須在Predicate Function中顯式定義。一般情況下,會設置一個管理RLS的Manager用戶,用於維護Security Predicate控制的數據,必要時對數據處理進行故障排除。如果安全策略(Security Policy)被禁用,那么,用戶在訪問數據表時,不會Filter或Block任何數據行,看到的數據表的全部數據行。
1,啟用或禁用安全策略(Security Policy)
--diable ALTER SECURITY POLICY rls.SalesFilter WITH (STATE = OFF); --enable ALTER SECURITY POLICY rls.SalesFilter WITH (STATE = ON);
2,更新安全策略
用戶可以把安全策略應用到多個表上:
ALTER SECURITY POLICY rls.SalesFilter ADD FILTER PREDICATE rls.fn_securitypredicate(SalesRep) ON dbo.FactSales;
3,查看系統中的安全策略和斷言
select p.object_id as security_policy_id ,s.name as security_policy_name ,s.is_enabled as security_policy_state ,s.is_schema_bound ,p.security_predicate_id ,p.target_object_id ,schema_name(o.schema_id)+'.'+object_name(p.target_object_id) as target_object_name ,p.predicate_definition ,p.predicate_type_desc ,p.operation_desc from sys.security_predicates p inner join sys.security_policies s on p.object_id=s.object_id inner join sys.objects o on p.target_object_id=o.object_id
五,RLS對查詢性能的影響
RLS會影響查詢的性能,RLS 過濾斷言在功能上等價於在查詢的(Where)上追加一個條件。在創建安全策略時,應該使斷言函數內的查詢足夠簡單。
參考文檔: