Accesso riga per riga di SQL Server


10

Ho una tabella strutturata in questo modo (semplificata)

Name, EMail, LastLoggedInAt

Ho un utente in SQL Server (RemoteUser) che dovrebbe essere in grado di vedere solo i dati (tramite una query di selezione) in cui il campo LastLoggdInAt non è null.

Sembra che posso farlo? È possibile?


Ecco l'argomento Libri online per la sicurezza a livello di riga: docs.microsoft.com/en-us/sql/relational-d database
David Browne - Microsoft,

Risposte:


32

Il modello di sicurezza di SQL Server consente di concedere l'accesso a una vista senza concedere l'accesso alle tabelle sottostanti.

Poiché il codice di esempio è un ottimo modo per mostrare un concetto, considera quanto segue, con una LoginDetailstabella e una vista corrispondente:

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

Creeremo un account di accesso e un utente, quindi assegneremo a quell'utente i diritti per selezionare le righe dalla vista, senza disporre dei diritti per visualizzare la tabella stessa.

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

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetailsView TO RemoteUser;

Ora inseriremo due righe di test:

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', 'x@y.com', NULL)
    , ('user y', 'y@y.com', GETDATE());

Questo verifica il modello di sicurezza. La prima SELECTistruzione ha SELECTesito positivo, poiché viene selezionata dalla vista, mentre la seconda istruzione ha esito negativo poiché l'utente non ha accesso diretto alla tabella.

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetailsView;
╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nome utente ║ Indirizzo e-mail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ utente y ║ y@y.com ║ 15/02/2018 07: 36: 54.490 ║
╚══════════╩══════════════╩═══════════════════════ ══╝
SELECT *
FROM dbo.LoginDetails;

REVERT

Nota i risultati dalla vista escludono la riga in cui si trova il LastLoggedInAtvalore NULL, come richiesto nella tua domanda.

La seconda SELECTistruzione rispetto alla tabella sottostante restituisce un errore:

Messaggio 229, livello 14, stato 5, riga 28
L'autorizzazione SELECT è stata negata sull'oggetto "LoginDetails", database "tempdb", schema "dbo".

Pulire:

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

In alternativa, se si dispone di SQL Server 2016 o versione successiva, è possibile utilizzare un predicato di sicurezza a livello di riga per impedire a determinati utenti di visualizzare righe con un LastLoggedInAtvalore NULL .

Innanzitutto, creiamo la tabella, un login, un utente per tale login e garantiamo l'accesso alla tabella:

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;

Quindi, inseriamo un paio di righe di esempio. Una riga con un valore null LastLoggedInAte una con un valore non null per quella colonna.

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', 'x@y.com', NULL)
    , ('user y', 'y@y.com', GETDATE());

Qui, stiamo creando una funzione con valori di tabella associata allo schema che restituisce una riga con 0 o 1 a seconda del valore delle variabili @LastLoggedInAte @usernameche vengono passate nella funzione. Questa funzione verrà utilizzata da un predicato filtro per eliminare le righe che vogliamo nascondere a determinati utenti.

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

Questo è il filtro di sicurezza che elimina le righe dalle SELECTistruzioni eseguite sulla dbo.LoginDetailstabella:

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

Il filtro sopra utilizza la dbo.fn_LoginDetailsRemoteUserPredicatefunzione passando il nome dell'utente corrente, insieme ai valori di ciascuna riga per la LastLoggedInAtcolonna dalla dbo.LoginDetailstabella.

Se eseguiamo una query sulla tabella come utente normale:

SELECT *
FROM dbo.LoginDetails

vediamo tutte le righe:

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nome utente ║ Indirizzo e-mail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ utente x ║ x@y.com ║ NULL ║
║ utente y ║ y@y.com ║ 2018-02-15 13: 53: 42.577 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

Tuttavia, se testiamo come RemoteUser:

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetails

REVERT

vediamo solo righe "valide":

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nome utente ║ Indirizzo e-mail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ utente y ║ y@y.com ║ 2018-02-15 13: 42: 02.023 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

E puliamo:

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

Tenere presente che l'associazione di uno schema a una funzione in questo modo rende impossibile modificare la definizione della tabella senza prima eliminare il predicato del filtro e la dbo.fn_LoginDetailsRemoteUserPredicatefunzione.


Risposta brillante - grazie! Qual è l'implicazione sulle prestazioni di questi 2 metodi. Abbiamo scoperto che la nostra app Web è fino a 5 volte più lenta quando utilizziamo la funzione. Devi guardare il metodo di visualizzazione.
LiamB,

La funzione di sicurezza a livello di riga viene valutata per ogni riga letta dalla tabella; Mi aspetto che rallenti in modo significativo l'accesso a tale tabella. La vista, d'altra parte, dovrebbe avere un impatto trascurabile sulle prestazioni presupponendo che si crei un indice utile sulla LastLoggedInAtcolonna.
Max Vernon,

Questo ha senso: guarderò il panorama ora, sembra funzionare bene! Se volessimo che l'utente fosse in grado di modificare solo i dati dell'utente per queste righe che soddisfano i criteri sarebbe possibile con la vista?
LiamB

È bello, tutto funzionante - grazie per l'aiuto con questo
LiamB

Sì, è possibile modificare le righe tramite la vista
Max Vernon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.