Acceso de fila por fila de SQL Server

10

Tengo una tabla estructurada así (Simplificado)

Name, EMail, LastLoggedInAt

Tengo un usuario en SQL Server (RemoteUser) que solo debería poder ver los datos (a través de una consulta de selección) donde el campo LastLoggdInAt no es nulo.

Parece que puedo hacer esto? ¿Es posible?

LiamB
fuente
Aquí está el tema de los Libros en línea para la seguridad de nivel de fila: docs.microsoft.com/en-us/sql/relational-databases/security/…
David Browne - Microsoft

Respuestas:

32

El modelo de seguridad de SQL Server le permite otorgar acceso a una vista sin otorgar acceso a las tablas subyacentes.

Dado que el código de ejemplo es una excelente manera de mostrar un concepto, considere lo siguiente, con una LoginDetailstabla y la vista correspondiente:

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

Crearemos un inicio de sesión y un usuario, luego le asignaremos a ese usuario los derechos para seleccionar filas de la vista, sin tener ningún derecho para ver la tabla en sí.

CREATE LOGIN RemoteUser 
WITH PASSWORD = '2q1345lkjsadfgsa0(*';

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetailsView TO RemoteUser;

Ahora, insertaremos dos filas de prueba:

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', '[email protected]', NULL)
    , ('user y', '[email protected]', GETDATE());

Esto prueba el modelo de seguridad. La primera SELECTinstrucción tiene éxito, ya que está seleccionando desde la vista, mientras que la segunda SELECTfalla porque el usuario no tiene acceso directo a la tabla.

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetailsView;
╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nombre de usuario ║ Dirección de correo electrónico ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ usuario y ║ [email protected] ║ 2018-02-15 07: 36: 54.490 ║
╚══════════╩══════════════╩═══════════════════════ ══╝
SELECT *
FROM dbo.LoginDetails;

REVERT

Tenga en cuenta que los resultados de la vista excluyen la fila donde está el LastLoggedInAtvalor NULL, como se requiere en su pregunta.

La segunda SELECTdeclaración contra la tabla subyacente devuelve un error:

Msg 229, Nivel 14, Estado 5, Línea 28
El permiso SELECT fue denegado en el objeto 'LoginDetails', base de datos 'tempdb', esquema 'dbo'.

Limpiar:

DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP VIEW dbo.LoginDetailsView;
DROP TABLE dbo.LoginDetails;

Alternativamente, si tiene SQL Server 2016 o más reciente, puede usar un predicado de seguridad de nivel de fila para evitar que ciertos usuarios vean filas con un LastLoggedInAtvalor NULL .

Primero, creamos la tabla, un inicio de sesión, un usuario para ese inicio de sesión y otorgamos acceso a la tabla:

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;

A continuación, insertamos un par de filas de muestra. Una fila con un valor nulo LastLoggedInAty otra con un valor no nulo para esa columna.

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', '[email protected]', NULL)
    , ('user y', '[email protected]', GETDATE());

Aquí, estamos creando una función de tabla con valores de esquema que devuelve una fila con 0 o 1 dependiendo del valor de las variables @LastLoggedInAty @usernameque se pasan a la función. Esta función será utilizada por un predicado de filtro para eliminar las filas que queremos ocultar a ciertos usuarios.

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

Este es el filtro de seguridad que elimina las filas de las SELECTdeclaraciones ejecutadas en la dbo.LoginDetailstabla:

CREATE SECURITY POLICY LoginDetailsRemoteUserPolicy
ADD FILTER PREDICATE dbo.fn_LoginDetailsRemoteUserPredicate(LastLoggedInAt, USER_NAME())
ON dbo.LoginDetails
WITH (STATE=ON);

El filtro anterior usa la dbo.fn_LoginDetailsRemoteUserPredicatefunción al pasar el nombre del usuario actual, junto con los valores de cada fila para la LastLoggedInAtcolumna de la dbo.LoginDetailstabla.

Si consultamos la tabla como un usuario normal:

SELECT *
FROM dbo.LoginDetails

vemos todas las filas:

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nombre de usuario ║ Dirección de correo electrónico ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ usuario x ║ [email protected] ║ NULL ║
║ usuario y ║ [email protected] ║ 2018-02-15 13: 53: 42.577 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

Sin embargo, si probamos como RemoteUser:

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetails

REVERT

solo vemos filas "válidas":

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nombre de usuario ║ Dirección de correo electrónico ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ usuario y ║ [email protected] ║ 2018-02-15 13: 42: 02.023 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

Y, limpiamos:

DROP SECURITY POLICY LoginDetailsRemoteUserPolicy;
DROP FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate;
DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP TABLE dbo.LoginDetails;

Tenga en cuenta que el enlace de esquema de una función a la tabla de esta manera hace que sea imposible modificar la definición de la tabla sin eliminar primero el predicado del filtro y la dbo.fn_LoginDetailsRemoteUserPredicatefunción.

Max Vernon
fuente
Respuesta brillante: ¡gracias! ¿Cuál es la implicación de rendimiento de estos 2 métodos? Descubrimos que nuestra aplicación web es hasta 5 veces más lenta cuando usamos la función. Necesito mirar el Método de Vista.
LiamB
La función de seguridad de nivel de fila se evalúa para cada fila leída de la tabla; Esperaría que reduzca significativamente el acceso a esa tabla. La vista, por otro lado, debería tener un impacto insignificante en el rendimiento, suponiendo que cree un índice útil en la LastLoggedInAtcolumna.
Max Vernon
Eso tiene sentido: ¡miraré la vista ahora, parece estar funcionando bien! Si quisiéramos que el usuario solo pudiera editar los datos del usuario para estas filas que coinciden con los criterios, ¿sería posible con la vista?
LiamB
Es hermoso, todo funciona - gracias por la ayuda con esto
LiamB
Sí, puede editar filas a través de la vista
Max Vernon