Answers:
SQL Server安全模型允许您授予对视图的访问权限,而无需授予对基础表的访问权限。
由于示例代码是展示概念的好方法,因此请考虑以下内容,并附有LoginDetails
表格和相应的视图:
CREATE TABLE dbo.LoginDetails
(
Username nvarchar(100) NOT NULL
, EmailAddress nvarchar(256) NOT NULL
, LastLoggedInAt datetime NULL
);
GO
CREATE VIEW dbo.LoginDetailsView
AS
SELECT ld.Username
, ld.EmailAddress
, ld.LastLoggedInAt
FROM dbo.LoginDetails ld
WHERE ld.LastLoggedInAt IS NOT NULL;
GO
我们将创建一个登录名和一个用户,然后为该用户分配从视图中选择行的权限,而没有查看表本身的任何权限。
CREATE LOGIN RemoteUser
WITH PASSWORD = '2q1345lkjsadfgsa0(*';
CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;
GRANT SELECT ON dbo.LoginDetailsView TO RemoteUser;
现在,我们将插入两个测试行:
INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', 'x@y.com', NULL)
, ('user y', 'y@y.com', GETDATE());
这将测试安全模型。第一条SELECT
语句成功,因为它正在从视图中进行选择,而第二条SELECT
语句失败,因为用户没有直接访问表的权限。
EXECUTE AS LOGIN = 'RemoteUser';
SELECT *
FROM dbo.LoginDetailsView;
╔══════════╦══════════════╦═══════════════════════ ══╗ ║用户名║电子邮件地址║LastLoggedInAt║ ╠══════════╬══════════════╬═══════════════════════ ══╣ ║用户y║y@y.com║2018-02-15 07:36:54.490║ ╚══════════牛皮══════════════牛皮═══════════════════════ ══╝
SELECT *
FROM dbo.LoginDetails;
REVERT
请注意,视图中的结果根据您的问题排除了LastLoggedInAt
值为的行NULL
。
SELECT
针对基础表的第二条语句返回错误:
消息229,级别14,状态5,第28行
。对对象“ LoginDetails”,数据库“ tempdb”,模式“ dbo”的SELECT权限被拒绝。
清理:
DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP VIEW dbo.LoginDetailsView;
DROP TABLE dbo.LoginDetails;
或者,如果您具有SQL Server 2016或更高版本,则可以使用行级安全性谓词来防止某些用户看到具有NULL LastLoggedInAt
值的行。
首先,我们创建表,登录名,该登录名的用户,然后授予对该表的访问权限:
CREATE TABLE dbo.LoginDetails
(
Username nvarchar(100) NOT NULL
, EmailAddress nvarchar(256) NOT NULL
, LastLoggedInAt datetime NULL
);
GO
CREATE LOGIN RemoteUser
WITH PASSWORD = '2q1345lkjsadfgsa0(*';
CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;
GRANT SELECT ON dbo.LoginDetails TO RemoteUser;
接下来,我们插入几个示例行。该列的一行为null LastLoggedInAt
,另一行的值为非null。
INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', 'x@y.com', NULL)
, ('user y', 'y@y.com', GETDATE());
在这里,我们正在创建一个与模式绑定的表值函数,该函数将根据传递给函数的@LastLoggedInAt
和@username
变量的值返回0或1的行。过滤谓词将使用此函数来消除我们要向某些用户隐藏的行。
CREATE FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate
(
@LastLoggedInAt datetime
, @username sysname
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_securitypredicate_result
WHERE (@username = N'RemoteUser' AND @LastLoggedInAt IS NOT NULL)
OR @username <> N'RemoteUser';
GO
这是安全过滤器,用于消除SELECT
针对dbo.LoginDetails
表运行的语句中的行:
CREATE SECURITY POLICY LoginDetailsRemoteUserPolicy
ADD FILTER PREDICATE dbo.fn_LoginDetailsRemoteUserPredicate(LastLoggedInAt, USER_NAME())
ON dbo.LoginDetails
WITH (STATE=ON);
上面的过滤器dbo.fn_LoginDetailsRemoteUserPredicate
通过传入当前用户的名称以及表中LastLoggedInAt
列的每一行的值来使用该功能dbo.LoginDetails
。
如果我们以普通用户身份查询表:
SELECT *
FROM dbo.LoginDetails
我们看到所有行:
╔══════════╦══════════════╦═══════════════════════ ══╗ ║用户名║电子邮件地址║LastLoggedInAt║ ╠══════════╬══════════════╬═══════════════════════ ══╣ ║用户x║x@y.com║空║ ║用户y║y@y.com║2018-02-15 13:53:42.577║ ╚══════════牛皮══════════════牛皮═══════════════════════ ══╝
但是,如果我们测试为RemoteUser
:
EXECUTE AS LOGIN = 'RemoteUser';
SELECT *
FROM dbo.LoginDetails
REVERT
我们只会看到“有效”行:
╔══════════╦══════════════╦═══════════════════════ ══╗ ║用户名║电子邮件地址║LastLoggedInAt║ ╠══════════╬══════════════╬═══════════════════════ ══╣ ║用户y║y@y.com║2018-02-15 13:42:02.023║ ╚══════════牛皮══════════════牛皮═══════════════════════ ══╝
并且,我们清理:
DROP SECURITY POLICY LoginDetailsRemoteUserPolicy;
DROP FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate;
DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP TABLE dbo.LoginDetails;
请注意,以这种方式将函数绑定到表确实会导致在不先删除过滤谓词和dbo.fn_LoginDetailsRemoteUserPredicate
函数的情况下无法修改表的定义。
LastLoggedInAt
列上创建了有用的索引,则该视图对性能的影响应该忽略不计。