SQL Server 2016 CPT3中包含了一个新特性叫Row Level Security(RLS),允许数据库管理员根据业务需要依据客户端执行脚本的一些特性控制客户端能够访问的数据行,比如,我们希望业务部的经理只能查看他所在部门的员工的薪资情况。以往像要实现这样的功能,都是要通过视图里层的逻辑编写来实现。以前某个项目就是这么实现的。或者通过在应用程序层去实现,比如在提交命令到数据库前,通过在查询语句中添加WHERE字句条件来实现数据过滤。这样显然RLS是更加简便的去实现行级别权限控制。

那么它是怎么实现的呢?结合SECURITY POLICY和内联函数

1)SECURITY POLICY作用于表,指明PREDICATE类型以及PREDICATE的参数传入;

2)PREDICATE的定义在内联函数里面;

这么理解吧。PREDICATE的中文意思就是谓语。对SQL Server查询语句执行计划了解的人对这个词肯定不会陌生。筛选条件在执行计划中的表示就是谓语(predicate)。那么这么看其实RLS的实现就是通过表中和某个或者多个字段属性作为引用(参考)条件,而内联函数的作为只不过是封装了谓语(predicate)的定义。定义本身是基于业务而定,所以有开发人员自身去决定。但是作为封装容器,还是需要有人去引用它,那么就是SECURITY POLICY啦。所以SECURITY POLICY成了表与谓语定义的间的纽带。

即:Table <- SECURITY POLICY -> Inline Function

RLS支持两种类型的PREDICATE:1)FILTER PREDICATE;2)BLOCK PREDICATE。两者的区别是什么呢?前者对数据读取操作有效,后者对增删改有效。前者是把违反了谓语的数据行过滤掉,而后者在针对违反了谓语的数据行进行增删改操作的时候触发错误。但是其实FILTER PREDICATE对于更新和删除是生效的,因为更新和删除第一步是先读取数据行嘛。

它们各自的共同点是:

1)内联函数中引用的其他表示不需要考虑是否具有查看权限的;

2)如果SECURITY POLICY的STAT=OFF,那么数据行筛选就失效了;

3)就算是dbo或者db_owner也会受到SECURITY POLICY的影响;

4)因为创建用于SECURITY POLICY的内联函数需要架构绑定,因此如果参考列被更新会触发错误,但是表中的别的栏位不会,同样的修改内联函数也会触发错误;

5)一张表中只可以对某种操作有一个谓语,比如不可以定义多个BEFORE UPDATE的BLOCK PREDICATE;

对于BLOCK PREDICATE:

1)虽然它类似于TRIGGER,但是其实两者是不一样。TRIGGER记录着修改前后的行的栏位数值,而BLOCK PREDICATE只会知道之前或者之后的值(取决于BEFORE UPDATE或者AFTER UPDATE)

2)只要违反了参考栏位的谓语的行才不可以操作;

3)对于BULK INSERT同样生效;

讲了这么多,那么其实RLS相当于用一层WHERE条件暗地里屏蔽掉了一些数据行。

微软的建议:

1)为用于RLS的内联函数和SECURITY POLICY创建单独的schema。

2)要创建SECIRITY POLICY需要有ALTER ANY SECURITY POLICY的权限。

3)避免数据类型转换而造成内联函数内就报错。

4)避免在内联函数中过多的表连接查询。

有一点需要注意:可以对视图应用RLS,但是如果应用了RLS的表被视图引用了,视图是无法创建索引视图的。

RLS和Columnstore索引、CDC、Memory-Optimized Tables是兼容的

MSDN上就给出了一个非常典型的例子,就是员工只能看到他自己的工资,而经理可以看到他下属员工的工资。

第一步先创建好用户和元数据

CREATE USER Martin WITHOUT LOGIN;
CREATE USER Sara WITHOUT LOGIN;
CREATE USER Amy WITHOUT LOGIN; CREATE TABLE dbo.Personnel
(
EmployeeID INT,
Name NVARCHAR(100),
Department NVARCHAR(100),
Position NVARCHAR(100),
ReportTo INT,
Salary FLOAT
);
GO INSERT INTO dbo.Personnel
(
EmployeeID,
Name,
Department,
Position,
ReportTo,
Salary
)
VALUES
(
1,
'Martin',
'Accounting',
'Manager',
NULL,
10000
);
GO INSERT INTO dbo.Personnel
(
EmployeeID,
Name,
Department,
Position,
ReportTo,
Salary
)
VALUES
(
2,
'Sara',
'Accounting',
'Accountant',
1,
5000
);
GO INSERT INTO dbo.Personnel
(
EmployeeID,
Name,
Department,
Position,
ReportTo,
Salary
)
VALUES
(
3,
'Amy',
'Accounting',
'Accountant',
1,
3000
);
GO

创建Schema和内联函数

CREATE SCHEMA Security;
GO CREATE FUNCTION Security.fn_SecurityPredicate(@Name AS SYSNAME)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_SecurityPredicate_result
WHERE @Name = USER_NAME() OR USER_NAME() = 'Martin';
GO

创建Security Policy

CREATE SECURITY POLICY SECPOL_PersonnelFilter
ADD FILTER PREDICATE Security.fn_SecurityPredicate(Name,Position)
ON dbo.Personnel
WITH (STATE = ON);

授权给三个数据库用户访问表的权限

GRANT SELECT  ON dbo.Personnel TO [Amy]
GRANT SELECT ON dbo.Personnel TO [Martin]
GRANT SELECT ON dbo.Personnel TO [Sara]

开始测试用户Amy能访问多少行数据

EXECUTE AS USER = 'Amy';
SELECT USER_NAME(),* FROM dbo.Personnel;
REVERT;

结果

测试用户Martin

EXECUTE AS USER = 'Martin';
SELECT USER_NAME(),* FROM dbo.Personnel;
REVERT;

结果

测试用户Sara

EXECUTE AS USER = 'Sara';
SELECT USER_NAME(),* FROM dbo.Personnel;
REVERT;

结果

RLS确实是SQL Server 2016的一个很有用处的特性。只是它身上也有一些”缺点“。以上面这个例子来讲,如果你需要实现CEO可以知道整个公司的工资,然后部门经理可以知道整个部门的员工的工资,甚至更复杂点。那么上面那个内联函数就变得复杂了,需要JOIN一些表去判定用户的职位高低来确定他的访问权限范围。而我们都知道在内联函数中加入表引用是很容易引发性能问题的。所以其实像上面这样的办法不是一个好的方案。真正解决办法其实是通过把用户的安全上下文存为一个字符串的变量的形式传入给内联函数,内联函数内部再去利用好这个安全上下文的信息去定义好数据访问的安全控制逻辑。这点在SQL Server 2016提供了一个SESSION_CONTEXT标量函数来配合实现这一方法。这种方法才可能是今后在SQL Server 2016 RTM发布后在现实中去使用RLS的Best Practices。

参考:

Row-Level Security

ALTER SECURITY POLICY (Transact-SQL)
DROP SECURITY POLICY (Transact-SQL)
sys.security_policies (Transact-SQL)
sys.security_predicates (Transact-SQL)

SQL Server ->> 深入探讨SQL Server 2016新特性之 --- Row-Level Security(行级别安全控制)的更多相关文章

  1. SQL Server ->> 深入探讨SQL Server 2016新特性之 --- Temporal Table(历史表)

    原文:SQL Server ->> 深入探讨SQL Server 2016新特性之 --- Temporal Table(历史表) 作为SQL Server 2016(CTP3.x)的另一 ...

  2. SQL Server 2016新特性:列存储索引新特性

    SQL Server 2016新特性:列存储索引新特性 行存储表可以有一个可更新的列存储索引,之前非聚集的列存储索引是只读的. 非聚集的列存储索引支持筛选条件. 在内存优化表中可以有一个列存储索引,可 ...

  3. SQL Server 2016新特性:DROP IF EXISTS

    原文:SQL Server 2016新特性:DROP IF EXISTS  在我们写T-SQL要删除某个对象(表.存储过程等)时,一般会习惯先用IF语句判断该对象是否存在,然后DROP,比如: 旧 ...

  4. atitit.Windows Server 2003 2008 2012系统的新特性 attilax 总结

    atitit.Windows Server 2003  2008  2012系统的新特性 attilax 总结 1. Windows Server 2008 新特性也可以归纳为4个方面. 1 2. 相 ...

  5. SQL Server 2016新特性:In-Memory OLTP

    存储格式修改 在2014,2016中修改了内存优化表的存储格式,新的格式是序列的并且the database is restarted once during database recovery.   ...

  6. SQL Server ->> SQL Server 2016新特性之 --- Query Store

    前言 SQL Server 2016引入新的查询语句性能监控.调试和优化工具/功能 -- Query Store.以前我们发现一条查询语句性能突然下降,我们要去找出问题的所在往往需要通过调用一些DMV ...

  7. SQL Server ->> SQL Server 2016新特性之 -- Dynamic Data Masking

    Dynamic Data Masking是为了防止敏感数据暴露给未经授权的用户,以一种最小开销和维护成本的形式.Dynamic Data Masking用于表的字段,相当于盖住字段数据的一部分.比如一 ...

  8. SQL Server ->> SQL Server 2016新特性之 -- sp_set_session_context存储过程和SESSION_CONTEXT函数

    sp_set_session_context存储过程和SESSION_CONTEXT函数出现在了SQL Server 2016 CTP3.0上.它俩配合起来的作用是sp_set_session_con ...

  9. sql server 2016新特性 查询存储(Query Store)的性能影响

    前段时间给客户处理性能问题,遇到一个新问题, 客户的架构用的是 alwayson ,并且硬件用的是4路96核心,内存1T ,全固态闪存盘,sql server 2016 . 问题  描述 客户经常出现 ...

随机推荐

  1. Java中String与Date格式之间的转换

    转自:https://blog.csdn.net/angus_17/article/details/7656631 经常遇到string和date之间的转换,把相关的内容总结在这里吧: 1.strin ...

  2. SqlServer索引优化 查看碎片情况

    本文引自 DBCC DBREINDEX重建索引提高SQL Server性能 查看碎片情况使用  dbcc showcontig 函数来进行 代码: --改成当前库 use DB_Name --创建变量 ...

  3. (转)Apache和Nginx运行原理解析

    Apache和Nginx运行原理解析 原文:https://www.server110.com/nginx/201402/6543.html Web服务器 Web服务器也称为WWW(WORLD WID ...

  4. sencha touch 手势识别左右滑动

    sencha touch 中添加手势识别非常简单,就是监听 dom 元素的 move 事件: 1. 为你的 view 注册 swipe 事件 // 为当前 view 注册手势滑动事件 Ext.get( ...

  5. php将“\\”转换成“\”的例子

    str_replace('\\\\', '\\', $url);

  6. js中有关类、对象的增强函数

    javascript中继承的实现 基础实现 function Range(from,to){ this.from =from; this.to =to; } Range.prototype = { i ...

  7. currentStyle、getComputedStyle 获取样式

    style.height 获取的是行间的样式 currentStyle.height.getComputedStyle(elem,null).height 获取的是 div 的 content 的宽高 ...

  8. JS实现中英文混合文字溢出友好截取功能

    在显示字符串的时候,避免字符串过长往往会对字符串进行截取操作,通常会用到js的 substr 或者 substring方法, 以及 字符串的length属性 substr() 方法可在字符串中抽取从 ...

  9. 微信小程序整理

    目录 开发环境 目录结构 WXML组件 WXSS 数据绑定 条件渲染 列表渲染 模版 事件 引用 路由传参 API 实例TodoList 1.开发环境 开发工具下载(https://mp.weixin ...

  10. requset获取post提交的请求参数

    1.请求体的内容通常是通过post来提交的,格式是 username=zhansan&password=123&hobby=football||&hobby=basketbal ...