Рядок SQL Server за доступом до рядків


10

У мене така структура структурована (спрощена)

Name, EMail, LastLoggedInAt

У мене є користувач у SQL Server (RemoteUser), який повинен мати змогу бачити дані (через запит вибору), де поле LastLoggdInAt не є нульовим.

Схоже, я можу це зробити? Це можливо?


Ось тема "Книги в Інтернеті" для безпеки на рівні рівнів: docs.microsoft.com/en-us/sql/relational-databases/security/…
Девід Браун - Microsoft

Відповіді:


32

Модель безпеки 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твердження проти основної таблиці повертає помилку:

Msg 229, рівень 14, стан 5, рядок 28
Дозвіл SELECT відмовлено в об’єкті "LoginDetails", базі даних "tempdb", схемі "dbo".

Прибирати:

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

Крім того, якщо у вас є SQL Server 2016 або новішої версії, ви можете використовувати предикат рівня безпеки на рядок, щоб певні користувачі не бачили рядки зі LastLoggedInAtзначенням NULL .

Спочатку ми створюємо таблицю, логін, користувача для цього входу, і надаємо доступ до таблиці:

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;

Далі вставляємо пару зразків рядків. Один рядок з нульовим значенням LastLoggedInAt, а інший із значенням, що не має значення для цього стовпця.

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

Тут ми створюємо функцію, пов’язану з таблицею-значення-значення, яка повертає рядок з 0 або 1 залежно від значення @LastLoggedInAtта @usernameзмінних, переданих у функцію. Ця функція буде використана предикатом фільтра для усунення рядків, які ми хочемо приховати від певних користувачів.

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 ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ user x ║ x@y.com ║ NULL ║
║ користувач 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функції.


Блискуча відповідь - дякую! Що означає ефективність цих двох методів. Ми виявили, що наш веб-додаток у 5 разів повільніше, коли ми використовуємо цю функцію. Потрібно переглянути метод перегляду.
LiamB

Функція захисту рівня рядків оцінюється для кожного рядка, прочитаного з таблиці; Я б очікував, що це значно сповільнить доступ до цієї таблиці. З іншого боку, вигляд повинен мати незначний вплив на продуктивність, якщо ви створили корисний індекс у LastLoggedInAtстовпці.
Макс Вернон

Це має сенс - я зараз погляну на вид, здається, працює добре! Якби ми хотіли, щоб користувач міг редагувати лише дані користувачів для цих рядків, які відповідають критеріям, чи можливо це було б із можливістю перегляду?
LiamB

Це прекрасно, все працює - дякую за допомогу в цьому
LiamB

Так, ви можете редагувати рядки через перегляд
Макс Вернон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.