SQL Server wiersz po wierszu dostępu

10

Mam taką strukturę tabeli (uproszczonej)

Name, EMail, LastLoggedInAt

Mam użytkownika w programie SQL Server (RemoteUser), który powinien widzieć tylko dane (za pośrednictwem kwerendy Select), w których pole LastLoggdInAt nie ma wartości null.

Wygląda na to, że mogę to zrobić? Czy to możliwe?

LiamB
źródło
Oto temat Books Online dotyczący zabezpieczeń na poziomie wiersza
David Browne - Microsoft

Odpowiedzi:

32

Model zabezpieczeń programu SQL Server umożliwia udzielanie dostępu do widoku bez udzielania dostępu do bazowych tabel.

Ponieważ przykładowy kod to świetny sposób na pokazanie koncepcji, weź pod uwagę następujące kwestie wraz z LoginDetailstabelą i odpowiednim widokiem:

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

Utworzymy login i użytkownika, a następnie przypiszemy temu użytkownikowi prawa do wyboru wierszy z widoku, bez żadnych uprawnień do przeglądania samej tabeli.

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

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetailsView TO RemoteUser;

Teraz wstawimy dwa wiersze testowe:

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

To testuje model bezpieczeństwa. Pierwsza SELECTinstrukcja kończy się powodzeniem, ponieważ wybiera z widoku, natomiast druga SELECTinstrukcja kończy się niepowodzeniem, ponieważ użytkownik nie ma bezpośredniego dostępu do tabeli.

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetailsView;
╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nazwa użytkownika ║ Adres e-mail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ użytkownik y ║ [email protected] ║ 15.02.2018 07: 36: 54.490 ║
╚══════════╩══════════════╩═══════════════════════ ══╝
SELECT *
FROM dbo.LoginDetails;

REVERT

Uwaga: wyniki z widoku wykluczają wiersz, w którym LastLoggedInAtznajduje się wartość NULL, zgodnie z wymaganiami w pytaniu.

Druga SELECTinstrukcja względem tabeli bazowej zwraca błąd:

Msg 229, poziom 14, stan 5, wiersz 28
Odmówiono uprawnienia SELECT do obiektu „LoginDetails”, bazy danych „tempdb”, schematu „dbo”.

Sprzątać:

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

Alternatywnie, jeśli masz SQL Server 2016 lub nowszy, możesz użyć predykatu zabezpieczeń na poziomie wiersza, aby uniemożliwić niektórym użytkownikom wyświetlanie wierszy o LastLoggedInAtwartości NULL .

Najpierw tworzymy tabelę, login, użytkownika dla tego loginu i udzielamy dostępu do tabeli:

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;

Następnie wstawiamy kilka przykładowych wierszy. Jeden wiersz z wartością null LastLoggedInAti jeden z wartością inną niż null dla tej kolumny.

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

W tym miejscu tworzymy funkcję tabelaryczną powiązaną ze schematem, która zwraca wiersz z 0 lub 1 w zależności od wartości @LastLoggedInAti @usernamezmiennych przekazywanych do funkcji. Ta funkcja będzie używana przez predykat filtra, aby wyeliminować wiersze, które chcemy ukryć przed niektórymi użytkownikami.

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

To jest filtr bezpieczeństwa, który eliminuje wiersze z SELECTinstrukcji uruchamianych względem dbo.LoginDetailstabeli:

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

Powyższy filtr używa tej dbo.fn_LoginDetailsRemoteUserPredicatefunkcji, przekazując nazwę bieżącego użytkownika wraz z wartościami z każdego wiersza dla LastLoggedInAtkolumny z dbo.LoginDetailstabeli.

Jeśli zapytamy tabelę jako zwykły użytkownik:

SELECT *
FROM dbo.LoginDetails

widzimy wszystkie rzędy:

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nazwa użytkownika ║ Adres e-mail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ użytkownik x ║ [email protected] ║ NULL ║
║ użytkownik y ║ [email protected] ║ 15.02.2018 13: 53: 42.577 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

Jeśli jednak przetestujemy jako RemoteUser:

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetails

REVERT

widzimy tylko „prawidłowe” wiersze:

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nazwa użytkownika ║ Adres e-mail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ użytkownik y ║ [email protected] ║ 15.02.2018 13: 42: 02.023 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

I sprzątamy:

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

Należy pamiętać, że powiązanie schematu w ten sposób z tabelą uniemożliwia modyfikację definicji tabeli bez uprzedniego upuszczenia predykatu filtru i dbo.fn_LoginDetailsRemoteUserPredicatefunkcji.

Max Vernon
źródło
Świetna odpowiedź - dzięki! Jaki jest wpływ na wydajność tych 2 metod. Odkryliśmy, że nasza aplikacja internetowa jest do 5 razy wolniejsza, gdy korzystamy z tej funkcji. Musisz spojrzeć na metodę wyświetlania.
LiamB,
Funkcja bezpieczeństwa na poziomie wiersza jest oceniana dla każdego wiersza odczytanego z tabeli; Spodziewałbym się, że znacznie spowolni dostęp do tego stołu. Z drugiej strony widok powinien mieć znikomy wpływ na wydajność przy założeniu, że utworzysz użyteczny indeks w LastLoggedInAtkolumnie.
Max Vernon
To ma sens - spojrzę teraz na widok, wydaje się, że działa dobrze! Gdybyśmy chcieli, aby użytkownik mógł edytować dane użytkownika tylko dla tych wierszy, które spełniają kryteria, czy byłoby to możliwe w widoku?
LiamB,
Jest piękny, wszystko działa - dzięki za pomoc w tym
LiamB
Tak, możesz edytować wiersze za pomocą widoku
Max Vernon