Perché la sicurezza a livello di riga è importante?
Prima di SQL Server 2016, la sicurezza a livello di tabella era il livello di sicurezza più basso predefinito per un database. In altre parole, un utente potrebbe essere limitato ad accedere a una tabella nel suo insieme. Tuttavia, in alcuni casi è necessario che gli utenti abbiano accesso a una tabella, ma non a righe specifiche all'interno della tabella. Prima di SQL Server 2016, ciò richiedeva la scrittura di stored procedure personalizzate per la fornitura di tale sicurezza a grana fine. Tuttavia, tali procedure memorizzate sono soggette a SQL injection e altri avvertimenti di sicurezza.
Utilizzo della funzionalità di sicurezza a livello di riga di SQL Server in pratica
SQL Server 2016 ha introdotto una nuova funzionalità di sicurezza a livello di riga che consente agli utenti di accedere a una tabella ma limita l'accesso a righe specifiche all'interno di tale tabella. Diamo un'occhiata a come questo può essere utilizzato praticamente.
Descrizione
Sono disponibili quattro passaggi per implementare la sicurezza a livello di riga in SQL Server.
- Concedi le autorizzazioni Select agli utenti sulla tabella su cui desideri implementare la sicurezza a livello di riga.
- Successivamente, devi scrivere una funzione di valore inline-table contenente un predicato di filtro. Aggiungi la logica del filtro al predicato del filtro.
- Infine, devi associare il predicato del filtro che hai creato nel secondo passaggio a una policy di sicurezza.
- Verifica la funzionalità di sicurezza a livello di riga.
Prima di eseguire i passaggi precedenti, è necessario creare un database fittizio con alcuni record fittizi. Esegui il seguente script per farlo:
CREATE DATABASE University GO USE University GO USE University CREATE TABLE Persons ( Id INT PRIMARY KEY IDENTITY(1,1), Name VARCHAR (50), Role VARCHAR (50) ) GO USE University INSERT INTO Persons VALUES ('Sally', 'Principal' ) INSERT INTO Persons VALUES ('Edward', 'Student' ) INSERT INTO Persons VALUES ('Jon', 'Student' ) INSERT INTO Persons VALUES ('Scot', 'Student') INSERT INTO Persons VALUES ('Ben', 'Student' ) INSERT INTO Persons VALUES ('Isabel', 'Teacher' ) INSERT INTO Persons VALUES ('David', 'Teacher' ) INSERT INTO Persons VALUES ('Laura', 'Teacher' ) INSERT INTO Persons VALUES ('Jean', 'Teacher') INSERT INTO Persons VALUES ('Francis', 'Teacher' )
Nello script creiamo un database fittizio "Università". Successivamente, eseguiamo lo script che crea una tabella denominata "Persone". Se guardi il design della tabella, puoi vedere che contiene tre colonne Id, Name e Role. La colonna Id è la colonna della chiave primaria con il vincolo IDENTITY. La colonna Nome contiene il nome della persona e la colonna Ruolo contiene il ruolo della persona. Infine, abbiamo inserito 10 record nella tabella Persone. Il tavolo ha 1 Preside, 4 insegnanti e 5 studenti.
Eseguiamo una semplice istruzione SELECT per vedere i record nella tabella:
Use University SELECT * FROM Persons
Il risultato è simile al seguente:
Vogliamo che l'utente denominato Principal abbia accesso a tutte le righe nella tabella Persons. Allo stesso modo, un Insegnante dovrebbe avere accesso solo ai record Insegnante, mentre gli Studenti dovrebbero avere accesso solo ai record Studente. Questo è un classico caso di sicurezza a livello di riga.
Per implementare la sicurezza a livello di riga, seguiremo i passaggi che abbiamo discusso in precedenza.
Passaggio 1:Concedi le autorizzazioni di selezione agli utenti sul tavolo
Creiamo tre utenti con ruoli Preside, Insegnante e Studente e concediamo loro l'accesso SELECT a questi utenti nella tabella Persone. Esegui il seguente script per farlo:
CREATE USER Principal WITHOUT LOGIN; GO CREATE USER Teacher WITHOUT LOGIN; GO CREATE USER Student WITHOUT LOGIN; GO Use University GRANT SELECT ON Persons TO Principal; GO GRANT SELECT ON Persons TO Teacher; GO GRANT SELECT ON Persons TO Student; GO
Passaggio 2:creazione del predicato filtro
Dopo che agli utenti sono state concesse le autorizzazioni, il passaggio successivo consiste nel creare un predicato di filtro.
Il seguente script lo fa:
Use University GO CREATE FUNCTION dbo.fn_SP_Person(@Role AS sysname) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS fn_SP_Person_output -- Predicate logic WHERE @Role = USER_NAME() OR USER_NAME() = 'Principal'; GO
Il predicato del filtro viene creato all'interno di una funzione inline con valori di tabella e assume il ruolo dell'utente come parametro. Restituisce quei record in cui il valore del ruolo passato come parametro corrisponde al valore del ruolo nella colonna del ruolo. Oppure, se il ruolo utente è "Principale", vengono restituiti tutti i ruoli. Se guardi il filtro dei predicati, non troverai il nome della tabella per cui stiamo creando il filtro. Il predicato del filtro è collegato alla tabella tramite la politica di sicurezza che vedremo nel passaggio successivo.
Fase 3:creazione di una politica di sicurezza
Esegui lo script seguente per creare una policy di sicurezza per il predicato del filtro che abbiamo creato nell'ultimo passaggio:
Use University Go CREATE SECURITY POLICY RoleFilter ADD FILTER PREDICATE dbo.fn_SP_Person(Role) ON dbo.Persons WITH (STATE = ON); GO
Nella politica di sicurezza, abbiamo semplicemente aggiunto il predicato del filtro che abbiamo creato alla tabella Persone. Per abilitare il criterio, il flag "STATE" deve essere impostato su ON.
Fase 4:verifica della sicurezza a livello di riga
Abbiamo eseguito tutti i passaggi necessari per imporre la sicurezza a livello di riga sulla tabella Persone del database di Ateneo. Proviamo innanzitutto ad accedere ai record nella tabella Persone tramite l'utente predefinito. Esegui il seguente script:
Use University SELECT * FROM Persons; GO
Non vedrai nulla nell'output poiché l'utente predefinito non può accedere alla tabella Persone.
Passiamo all'utente Studente che abbiamo creato in precedenza e proviamo a SELEZIONARE i record dalla tabella Persone:
EXECUTE AS USER = 'Student'; Use University SELECT * FROM Persons; -- Student Records Only REVERT; GO
Nello script sopra, passiamo all'utente "Studente", selezioniamo i record dalla tabella Persone e torniamo all'utente predefinito. L'output è simile a questo:
Puoi vedere che, a causa della sicurezza a livello di riga, vengono visualizzati solo i record in cui la colonna Ruolo ha un valore di Studente.
Allo stesso modo, l'utente Insegnante avrà accesso solo ai record in cui la colonna Ruolo ha il valore di Insegnante. Esegui il seguente script per verificarlo:
EXECUTE AS USER = 'Teacher'; Use University SELECT * FROM Persons; -- All Records REVERT; GO
Nell'output, troverai i seguenti record:
Infine, nel nostro predicato di filtro, abbiamo implementato la logica che l'utente Principal può accedere a tutti i record. Verifichiamolo eseguendo la seguente query:
EXECUTE AS USER = 'Principal'; Use University SELECT * FROM Persons; -- All Records REVERT; GO
Nell'output, vedrai tutti i record come mostrato di seguito:
Conclusione
La funzionalità di sicurezza a livello di riga è estremamente utile quando si desidera che gli utenti dispongano di un accesso dettagliato a dati specifici. Tuttavia, la funzionalità di sicurezza a livello di riga prevede la funzione inline con valori di tabella, che potrebbe causare un calo delle prestazioni.
Come regola generale, se prevedi di utilizzare una semplice clausola WHERE nella funzione del predicato, le tue prestazioni non dovrebbero essere influenzate. D'altra parte, le istruzioni di join complesse che coinvolgono tabelle di ricerca dovrebbero essere evitate quando hai implementato la sicurezza a livello di riga.