В современных информационных системах защита данных – одна из ключевых задач․ Часто требуется ограничить доступ не только к целой базе, но и к отдельным записям (строкам) и полям (столбцам) в зависимости от роли пользователя, его отделения, географии и прочих атрибутов․ В этой статье мы подробно разберём, как реализовать такие ограничения в популярных СУБД, какие подходы существуют на уровне СУБД и на уровне приложения, а также рассмотрим лучшие практики и типичные ошибки․
Содержание
- Основные концепции
- PostgreSQL: Row Level Security (RLS)
- Microsoft SQL Server: политики безопасности и динамические фильтры
- Oracle: VPD (Virtual Private Database) и колонные политики
- MySQL / MariaDB: обходные пути и плагины
- Контроль доступа на уровне приложения
- Лучшие практики
- Типичные подводные камни
Традиционный подход к контролю доступа в СУБД подразумевает предоставление пользователю прав на уровне базы или схемы (например, SELECT, INSERT, UPDATE, DELETE)․ При росте количества пользователей и усложнении бизнес‑логики такой «грубый» контроль становится недостаточным․ Примеров, когда нужен более тонкий гранулированный доступ, множество:
- Сотрудники разных отделов могут видеть только свои клиентские заявки․
- Менеджеры могут редактировать только те поля, которые относятся к их компетенции (например, только статус сделки, но не финансовую часть)․
- Регулятивные требования (GDPR, HIPAA) требуют скрывать персональные данные от большинства сотрудников․
- Мультиарендные (multitenant) приложения, где каждый клиент (арендатор) видит только свои данные․
Для решения этих задач применяются два основных механизма:
- Ограничения уровня записей (Row‑Level Security, RLS) – фильтрация строк в зависимости от контекста пользователя․
- Ограничения уровня полей (Column‑Level Security, CLS) – скрытие или маскирование отдельных столбцов․
Основные концепции
Контекст безопасности
Для применения RLS/CLS необходимо, чтобы СУБД «знала», кто сейчас выполняет запрос․ Это достигается через:
- Текущий пользователь (login) –
CURRENT_USER,SESSION_USER․ - Сессионные переменные (например,
SET ROLE,SET SESSIONв PostgreSQL)․ - Контекст выполнения (SQL Server –
SESSION_CONTEXT, Oracle –SYS_CONTEXT)․
Политики доступа
Политика – набор правил, определяющих, какие строки/столбцы доступны пользователю․ В большинстве СУБД политики реализуются в виде:
- Виртуальных представлений (VIEW) с предикатами․
- Триггеров/фильтров, которые автоматически добавляются к запросу․
- Плагинов/расширений, которые перехватывают запросы․
Модели реализации
| Модель | Плюсы | Минусы |
|---|---|---|
| Встроенные возможности СУБД (RLS, VPD) | Прозрачность, минимальный оверхед, централизованное управление․ | Не во всех СУБД поддерживается, иногда ограничена только строками․ |
| Вьюхи + функции | Работает в любой СУБД, гибко настраивается․ | Требует изменения кода запросов, может ухудшить производительность․ |
| Плагины/расширения | Максимальная гибкость, могут реализовать маскирование столбцов․ | Сложнее в поддержке, иногда требует перезапуска сервера․ |
PostgreSQL: Row Level Security (RLS)
Начиная с версии 9․5 PostgreSQL поддерживает встроенный механизм Row Level Security․ Он позволяет задавать политику доступа к таблице, которая будет автоматически применяться к каждому запросу․
Включение RLS для таблицы
-- Предположим, есть таблица orders
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
client_id INT NOT NULL,
amount NUMERIC(10,2),
status TEXT,
created_at TIMESTAMP DEFAULT now
);
-- Включаем RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
Создание политики
Допустим, каждый пользователь имеет роль sales_rep и может видеть только свои заказы (client_id = текущий пользователь)․
-- В качестве примера будем хранить id текущего пользователя в переменной
-- PostgreSQL позволяет задавать пользовательские переменные через set_config
-- (или использовать pg_catalog․current_user, если логины совпадают с client_id)
CREATE POLICY sales_rep_policy ON orders
USING (client_id = current_setting('app․current_user_id')::int);
Установка контекста в приложении
-- При каждом соединении приложение должно задать переменную:
SELECT set_config('app․current_user_id', '42', true);
-- Теперь любой запрос к orders будет отфильтрован:
SELECT * FROM orders; -- вернёт только строки, где client_id = 42
Ограничения уровня полей
PostgreSQL сам по себе не предоставляет CLS, но можно реализовать маскирование через VIEW или policy using + SELECT ․․․ EXCLUDE:
-- Создаём представление, скрывающее колонку amount для роли analyst
CREATE VIEW orders_public AS
SELECT
id,
client_id,
CASE
WHEN current_setting('app․role') = 'analyst' THEN NULL
ELSE amount
END AS amount,
status,
created_at
FROM orders;
Пример полной конфигурации
-- 1․ Включаем RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- 2․ Политика для менеджеров (видят все)
CREATE POLICY manager_policy ON orders
FOR SELECT USING (true) WITH CHECK (true);
-- 3․ Политика для продавцов (видят свои)
CREATE POLICY sales_rep_policy ON orders
FOR SELECT USING (client_id = current_setting('app․current_user_id')::int)
WITH CHECK (client_id = current_setting('app․current_user_id')::int);
-- 4․ Привязываем роли
GRANT SELECT ON orders TO sales_rep, manager;
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
После этого пользователи в роли sales_rep увидят только свои записи, а менеджеры – всё․
Microsoft SQL Server: политики безопасности и динамические фильтры
SQL Server реализует RLS начиная с версии 2016․ Основные компоненты:
- Фильтрующая функция (inline table‑valued function) – определяет предикат доступа․
- Политика безопасности (security policy) – связывает функцию с таблицей․
Пример функции фильтрации
CREATE SCHEMA Security;
GO
CREATE FUNCTION Security․fn_sales_rep_filter(@ClientId INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_securitypredicate_result
WHERE @ClientId = CAST(SESSION_CONTEXT(N'UserId') AS INT);
GO
CREATE SECURITY POLICY Security․SalesRepPolicy
ADD FILTER PREDICATE Security․fn_sales_rep_filter(ClientId) ON dbo․Orders
WITH (STATE = ON);
GO
Установка контекста из приложения
-- В ․NET:
using (var cmd = new SqlCommand("EXEC sp_set_session_context @key=N'UserId', @value=@uid", conn))
{
cmd․Parameters․AddWithValue("@uid", userId);
cmd․ExecuteNonQuery;
}
SQL Server поддерживает Dynamic Data Masking (DDM) – простой способ скрыть значения столбца․
ALTER TABLE dbo․Orders ALTER COLUMN Amount ADD MASKED WITH (FUNCTION = 'default');
-- Для роли, которой нужно видеть реальное значение, отключаем маскирование:
GRANT UNMASK TO manager_role;
Комбинация RLS и DDM
Можно одновременно использовать RLS (для строк) и DDM (для столбцов), получив гибкую схему доступа․
Oracle: VPD (Virtual Private Database) и колонные политики
Oracle реализует контроль доступа на уровне записей через Virtual Private Database (VPD) (пакет DBMS_RLS)․ Для колонок используется Data Redaction (начиная с 12c)․
Пример политики VPD
-- Предположим, таблица ORDERS
CREATE TABLE orders (
id NUMBER PRIMARY KEY,
client_id NUMBER,
amount NUMBER(10,2),
status VARCHAR2(20),
created_at DATE DEFAULT SYSDATE
);
-- Функция, возвращающая предикат
CREATE OR REPLACE FUNCTION security_pkg․sales_rep_predicate (
schema_name VARCHAR2,
table_name VARCHAR2
) RETURN VARCHAR2 IS
v_user_id NUMBER;
BEGIN
SELECT user_id INTO v_user_id FROM app_sessions WHERE session_id = SYS_CONTEXT('USERENV','SESSIONID');
RETURN 'client_id = ' || v_user_id;
END;
/
Привязка политики
BEGIN
DBMS_RLS․ADD_POLICY(
object_schema => 'HR',
object_name => 'ORDERS',
policy_name => 'sales_rep_policy',
function_schema => 'SECURITY_PKG',
policy_function => 'sales_rep_predicate',
statement_types => 'SELECT, INSERT, UPDATE, DELETE',
update_check => TRUE
);
END;
/
Data Redaction (маскирование колонок)
BEGIN
DBMS_REDACT․ADD_POLICY(
object_schema => 'HR',
object_name => 'ORDERS',
column_name => 'AMOUNT',
policy_name => 'mask_amount',
expression => 'CASE WHEN SYS_CONTEXT(''USERENV'', ''SESSION_USER'') = ''ANALYST'' THEN NULL ELSE amount END',
policy_type => DBMS_REDACT․FULL
);
END;
/
Пользователи с ролью ANALYST будут получать NULL вместо реального значения AMOUNT․
MySQL / MariaDB: обходные пути и плагины
В отличие от PostgreSQL и SQL Server, MySQL до версии 8․0 не имел встроенного RLS․ Возможные решения:
- Создание вьюх с предикатом, где в условие включается переменная сессии․
- Использование плагина
audit_logв комбинации с триггерами․ - Перенос логики контроля доступа в слой приложения․
Вьюха со сессионной переменной
-- При соединении задаём переменную:
SET @current_user_id = 42;
-- Вьюха, которая фильтрует строки:
CREATE VIEW orders_view AS
SELECT *
FROM orders
WHERE client_id = @current_user_id;
Недостаток: пользователь может изменить значение переменной, поэтому важно ограничить права на её изменение (GRANT EXECUTE ON ․․․)․
Плагин ProxySQL (для мультиарендных систем)
ProxySQL может маршрутизировать запросы к разным базам/схемам в зависимости от пользователя, тем самым реализуя «логический» RLS․
MariaDB Enterprise – Row Security Plugin
Начиная с MariaDB 10․5 существует коммерческий плагин mariadb-row-security, который реализует RLS аналогично PostgreSQL․
Контроль доступа на уровне приложения
Если СУБД не поддерживает нужные механизмы, часто используют подход «контроля доступа в коде»․
Преимущества
- Полный контроль над бизнес‑логикой․
- Возможность использовать любые условия (например, комбинацию ролей, географии, времени)․
Недостатки
- Повышенный риск ошибок (забытый фильтр в одном из запросов)․
- Труднее поддерживать единый набор правил при большом количестве микросервисов․
Типичный шаблон
// Псевдокод на Node․js (Express + Sequelize)
app․get('/orders', async (req, res) => {
const user = req․user; // уже аутентифицирован
const where = {};
if (user․role === 'sales_rep') {
where․clientId = user․id;
} else if (user․role === 'manager') {
// ничего не ограничиваем
} else {
return res․status(403)․send('Access denied');
}
const orders = await Order․findAll({ where });
res․json(orders);
});
Рекомендация
Если используете приложение‑уровневый контроль, обязательно внедрите единый сервис авторизации (OAuth2, OpenID Connect) и храните политики доступа в центральном хранилище (например, в OPA – Open Policy Agent)․
Лучшие практики
- Принцип «наименьших привилегий» – предоставляйте пользователям только те права, которые им действительно нужны․
- Сохраняйте однородность политики: если RLS реализовано в СУБД, не дублируйте её в приложении․
- Используйте именованные роли (sales_rep, manager, analyst) вместо прямых пользовательских логинов․
- Регулярно тестируйте политики с помощью unit‑тестов и сканеров безопасности․
- Ведите журналы доступа (audit) – в PostgreSQL это
pg_audit, в SQL Server –SQL Server Audit, в Oracle –AUDIT․ - При работе с конфиденциальными данными применяйте шифрование в покое и маскирование данных (Data Redaction, Dynamic Data Masking)․
- Для мультиарендных приложений рекомендуется использовать отдельные схемы или separate databases, а не только RLS․
- Обновляйте СУБД до последних патчей – в новых версиях часто появляются улучшения RLS/CLS․
Типичные подводные камни
| Проблема | Причина | Как избежать |
|---|---|---|
| Политика не применяется к UPDATE/DELETE | По умолчанию RLS может быть включён только для SELECT․ | Укажите FOR UPDATE и FOR DELETE в политике․ |
| Пользователь обходит ограничения через SUPERUSER | Суперпользователи игнорируют RLS; | Ограничьте количество суперпользователей; используйте роли без прав SUPERUSER․ |
| Маскирование колонок нарушает агрегатные функции | Masked значения могут быть NULL, что влияет на SUM, AVG․ | Определите отдельные «не замаскированные» представления для аналитики․ |
| Снижение производительности из‑за сложных предикатов | Фильтры RLS могут препятствовать использованию индексов․ | Создавайте покрывающие индексы по столбцам, участвующим в предикате․ |
| Неочевидные конфликты между политиками | Несколько политик могут «перекрываться» и давать неожиданные результаты․ | Тщательно проверяйте порядок применения политик, используйте USING и WITH CHECK правильно․ |
Ограничения прав доступа на уровне записей и полей позволяют построить безопасные, гибкие и соответствующие нормативным требованиям информационные системы․ Современные СУБД предоставляют встроенные механизмы (PostgreSQL RLS, SQL Server security policies, Oracle VPD, Data Redaction) и позволяют реализовать гибридные решения в комбинации с бизнес‑логикой приложения․
Ключевые выводы:
- Выбирайте встроенные возможности СУБД там, где они доступны – это проще в поддержке и быстрее в исполнении․
- Обеспечьте единую точку управления контекстом пользователя (session variables, JWT, OPA)․
- Не забывайте о маскировании колонок и шифровании данных при работе с конфиденциальной информацией․
- Регулярно тестируйте и аудируйте политики, чтобы избежать «проколов» в безопасности․
Следуя этим рекомендациям, вы сможете построить надёжную систему контроля доступа, которая будет масштабироваться вместе с ростом вашего бизнеса․