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列上创建了有用的索引,则该视图对性能的影响应该忽略不计。