本人新书上市,请多多关照:《SQL Server On Linux运维实战 2017版从入门到精通》
Row Level Security,行级安全性,简称RLS,是从SQL Server 2016引入。相对于过去,仅能对对象或语句进行权限控制从而对表的全部数据行实现权限管理而言,现在可以使用RLS针对特定的行进行授权。当然,视图也是可以做到的,但是RLS可能是更好的选择,因为RLS还实现了一些额外的功能用于在执行前或执行后进行一些阻止操作。
在介绍内部知识之前,先做一个演示,可能会更加深刻。但是在此之前先下载另外一个新的演示数据库WideWorldImporters ,下载方法可以参考前面的文章:SQL Server On Linux(3)——SQL Server 2019 For Linux 下载并部署示例数据库
USE master
GO
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = N'RLSTest')
BEGIN
CREATE LOGIN RLSTest
WITH PASSWORD = N'abc.123',
CHECK_POLICY = OFF,
CHECK_EXPIRATION = OFF,
DEFAULT_DATABASE = WideWorldImporters;
END
GO
这个角色映射到销售区域(sales territories)
USE WideWorldImporters;
GO
DROP USER IF EXISTS RLSTest
GO
CREATE USER RLSTest FOR LOGIN RLSTest
GO
ALTER ROLE [Great Lakes Sales] ADD MEMBER RLSTest --Great Lakes Sales已存在于该数据库中
GO
使用RLS需要创建一个SQL Server函数,用于确定查询可以访问哪些数据行。所以需要创建一个安全策略映射到这个函数。
-- 删除已存在的安全策略和函数
--
DROP SECURITY POLICY IF EXISTS [Application].FilterCustomersBySalesTerritoryRole
GO
DROP FUNCTION IF EXISTS [Application].DetermineCustomerAccess
GO
-- 创建用于RLS的函数
--
CREATE FUNCTION [Application].DetermineCustomerAccess(@CityID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (SELECT 1 AS AccessResult
WHERE IS_ROLEMEMBER(N'db_owner') <> 0
OR IS_ROLEMEMBER((SELECT sp.SalesTerritory
FROM [Application].Cities AS c
INNER JOIN [Application].StateProvinces AS sp
ON c.StateProvinceID = sp.StateProvinceID
WHERE c.CityID = @CityID) + N' Sales') <> 0
)
GO
-- 创建使用RLS函数的安全策略
--
CREATE SECURITY POLICY [Application].FilterCustomersBySalesTerritoryRole
ADD FILTER PREDICATE [Application].DetermineCustomerAccess
(DeliveryCityID)
ON Sales.Customers
GO
现在解释一下这个函数,它获取CityID的值,然后从Cities表和StateProvinces表中获取city的SalesTerritory名。然后把’Sales’追加到名字的末尾。这就任何在GreatLakes区域的CityID都会返回到GreatLakesSales中。
安全策略从[Sales].[Customers]表中返回DeliveryCityID。通过这个策略,如果以RLSTest用户登录并查询Customers表,那么只会返回符合规则的数据。下面就来测试一下。
首先使用sa来测试:
SELECT COUNT(*) FROM Sales.Customers;
然后我们以RLSTest身份来执行,这里并没有修改任何查询语句。
GRANT SELECT, UPDATE ON Sales.Customers TO [Great Lakes Sales];
GO
-- 以 RLSTest身份执行
EXECUTE AS USER = 'RLSTest'
GO
SELECT COUNT(*) FROM Sales.Customers;
GO
--收回权限
REVERT
GO
上图可以看到,用不同的用户执行相同的语句返回了不一样的结果,证明函数起效了。下面来做专业一点的介绍。
在过去,为了实现不同角色的用户访问不同的数据,即使是同一个表,也要进行多个视图控制或者架构等改变,这个操作需要从前端到后端一并实现,变更起来也很不方便。从SQL 2016开始,引入了RLS,相对于过去,RLS提供了相当简化的实现方式。
RLS提供了基于数据行的读写访问控制,并且最小化架构、应用程序或者查询语句的变更。
RLS有三个核心概念:
回看前面的谓词函数,需要注意的是函数要使用SCHEMABINDING,以免底层表的变更带来影响,但是仅仅创建函数是不够的,需要某个方式把函数应用到表上,这个时候就引出了安全策略。安全策略是安全谓词的集合,上面例子中仅对一个表进行操作,但是实际上可以对多个表进行操作,比如可以写成:
CREATE SECURITY POLICY AccountAndRepPolicy
ADD FILTER PREDICATE dbo.LimitAccountAccess(RepID) ON dbo.AccountReps,
ADD FILTER PREDICATE dbo.LimitAccountAccess(RepID) ON dbo.Accounts
WITH (STATE = ON);
这个安全策略把谓词函数dbo.LimitAccountAccess()应用到两个表中。通过ADD FILTER PREDICATE来实现。
下面来介绍一下RLS对“写”操作的行为,阻止谓词显式阻止违反该谓词的写入操作,包含AFTER INSERT、AFTER UPDATE、BEFORE UPDATE、BEFORE DELETE。
实现这种阻止操作,需要在安全策略中使用BLOCK谓词,在FILTER谓词过滤掉不可见的数据之后,再进行阻止,因为FILTER并不阻止数据插入。可以创建另外一个谓词函数,然后如下面添加ADD BLOCK PREDICATE来实现即可。这里就不演示了。
CREATE SECURITY POLICY [Application].FilterCustomersBySalesTerritoryRole
ADD FILTER PREDICATE [Application].DetermineCustomerAccess (DeliveryCityID) ON Sales.Customers
ADD BLOCK PREDICATE 阻止函数 ON Sales.Customers
GO
RLS是其中一个官方和SQL Server专家都推荐使用的安全功能,它类似触发器,也类似视图,RLS的其中一个亮点是简化了开发的工作量和复杂度。由于其功能强大,很多细节不打算在这里讲解,在后续再做一些案例演示,这篇的主要目的是引出这个功能,之所以没有做更多演示的原因是官方文档:行级安全性还是比较难度懂,我也在想如何能够在严谨的前提下做到易懂,所以在没做到这个程度之前先不引入得太深。