Мы часто сталкиваемся с ситуацией, когда RBAC трещит по швам. В одном проекте после внедрения RBAC мы получили 47 запросов на изменение ролей за месяц — каждое требовало согласования. ABAC сократил это до 2 запросов. Появляются правила вроде «пользователь может редактировать документ, если он его автор, документ находится в статусе draft, и пользователь работает в той же организации, что и документ». Роль тут не поможет — нужен контекст. ABAC (Attribute-Based Access Control) принимает решение на основе атрибутов субъекта (пользователя), объекта (ресурса) и среды (время, IP, контекст запроса). Наш опыт показывает, что гибрид RBAC+ABAC даёт оптимальный баланс производительности и гибкости. Мы гарантируем прозрачность решений с помощью детального аудита.
Как устроена модель
Четыре сущности в ABAC:
- Subject — пользователь и его атрибуты: role, department, clearance_level, org_id.
- Resource — объект и его атрибуты: owner_id, status, org_id, classification, region.
- Action — read, write, delete, approve.
- Environment — time_of_day, ip_address, request_method.
Политика — это предикат над этими атрибутами. Например:
ALLOW IF subject.org_id == resource.org_id AND (subject.role == 'editor' OR subject.id == resource.owner_id) AND resource.status IN ('draft', 'review') AND action == 'write' Почему ABAC лучше RBAC?
RBAC прост и быстр, но не масштабируется для сложных правил. ABAC позволяет выразить практически любое бизнес-ограничение: «менеджер может одобрить заявку, если сумма < 10000 и заявка создана в его отделе». В RBAC пришлось бы вводить новую роль. ABAC решает это декларативно. Сравним ключевые параметры:
| Критерий | RBAC | ABAC |
|---|---|---|
| Гибкость | Низкая (роли фиксированы) | Высокая (атрибуты) |
| Поддержка контекста | Нет | Да |
| Производительность | Высокая | Средняя (при большом числе политик) |
| Сложность внедрения | Низкая | Средняя |
Схема хранения политик
Можно хранить политики в коде (подходит для небольшого числа правил) или в базе с DSL. Вот вариант с хранением в PostgreSQL в виде JSON-условий:
CREATE TABLE abac_policies ( id SERIAL PRIMARY KEY, name VARCHAR(128) NOT NULL, description TEXT, effect VARCHAR(8) NOT NULL CHECK (effect IN ('allow', 'deny')), priority INT NOT NULL DEFAULT 0, conditions JSONB NOT NULL, -- дерево условий actions TEXT[] NOT NULL, resources TEXT[] NOT NULL -- glob: 'documents', 'documents/*' ); -- Пример записи INSERT INTO abac_policies (name, effect, priority, conditions, actions, resources) VALUES ( 'editors_can_write_own_draft', 'allow', 10, '{ "operator": "AND", "conditions": [ {"attribute": "subject.role", "op": "in", "value": ["editor", "senior_editor"]}, {"attribute": "subject.org_id", "op": "eq", "value": {"ref": "resource.org_id"}}, {"attribute": "resource.status", "op": "in", "value": ["draft", "review"]} ] }', ARRAY['write', 'delete'], ARRAY['documents', 'documents/*'] ); Движок принятия решений
class ABACEngine { constructor(policies) { // Политики предзагружены и отсортированы по приоритету (deny > allow при конфликте) this.policies = policies.sort((a, b) => { if (a.effect === 'deny' && b.effect !== 'deny') return -1; return b.priority - a.priority; }); } evaluate(subject, resource, action, environment = {}) { const context = { subject, resource, action, environment }; for (const policy of this.policies) { if (!policy.actions.includes(action)) continue; if (!this.matchesResource(policy.resources, resource.type)) continue; if (this.evaluateCondition(policy.conditions, context)) { return policy.effect === 'allow'; } } return false; // default deny } evaluateCondition(condition, ctx) { if (condition.operator === 'AND') { return condition.conditions.every(c => this.evaluateCondition(c, ctx)); } if (condition.operator === 'OR') { return condition.conditions.some(c => this.evaluateCondition(c, ctx)); } if (condition.operator === 'NOT') { return !this.evaluateCondition(condition.condition, ctx); } // Листовой узел const leftVal = this.resolveAttribute(condition.attribute, ctx); const rightVal = condition.value?.ref ? this.resolveAttribute(condition.value.ref, ctx) : condition.value; switch (condition.op) { case 'eq': return leftVal === rightVal; case 'neq': return leftVal !== rightVal; case 'in': return Array.isArray(rightVal) && rightVal.includes(leftVal); case 'gte': return leftVal >= rightVal; case 'lte': return leftVal <= rightVal; case 'contains': return Array.isArray(leftVal) && leftVal.includes(rightVal); default: return false; } } resolveAttribute(path, ctx) { // 'subject.org_id' → ctx.subject.org_id return path.split('.').reduce((obj, key) => obj?.[key], ctx); } matchesResource(patterns, resourceType) { return patterns.some(p => p === resourceType || (p.endsWith('/*') && resourceType.startsWith(p.slice(0, -2))) ); } } Как интегрировать ABAC в Express?
const engine = new ABACEngine(await loadPoliciesFromDB()); // Перезагрузка политик при изменении (без рестарта сервера) db.on('policy_changed', async () => { engine.updatePolicies(await loadPoliciesFromDB()); }); function abac(action) { return async (req, res, next) => { const resource = await loadResource(req); // загружаем объект со всеми атрибутами const allowed = engine.evaluate( req.user, // subject resource, // resource action, // action { // environment ip: req.ip, timestamp: Date.now(), userAgent: req.headers['user-agent'], } ); if (!allowed) { return res.status(403).json({ error: 'Forbidden' }); } req.resource = resource; next(); }; } router.put('/documents/:id', authenticate, abac('write'), updateDocument); router.delete('/documents/:id', authenticate, abac('delete'), deleteDocument); Audit log
ABAC без аудита — слепой инструмент. Каждое решение движка логируется:
CREATE TABLE abac_audit_log ( id BIGSERIAL PRIMARY KEY, ts TIMESTAMPTZ NOT NULL DEFAULT now(), subject_id INT NOT NULL, resource_type VARCHAR(128), resource_id VARCHAR(128), action VARCHAR(64) NOT NULL, decision BOOLEAN NOT NULL, matched_policy_id INT REFERENCES abac_policies(id), context_snapshot JSONB -- snapshot subject+resource attrs на момент решения ); CREATE INDEX idx_abac_audit_subject ON abac_audit_log (subject_id, ts DESC); CREATE INDEX idx_abac_audit_resource ON abac_audit_log (resource_type, resource_id, ts DESC); Это даёт ответ на вопрос «почему пользователь X не смог сделать Y с объектом Z три дня назад» — без него расследование инцидентов превращается в гадание.
Комбинирование с RBAC
Чистый ABAC медленнее RBAC при большом числе политик — каждая проверка проходит через все правила. На практике используют гибрид: RBAC как первый слой (быстрая грубая проверка по роли), ABAC как второй (тонкие контекстуальные правила только там, где нужно).
async function authorize(user, resource, action) { // Быстрый RBAC-check: есть ли у роли хоть какой-то доступ к этому типу ресурса? if (!await rbac.canAccessResourceType(user.role, resource.type)) { return false; // отсекаем без загрузки объекта и прохода по ABAC-политикам } // Тонкая проверка через ABAC return engine.evaluate(user, resource, action); } Как оценить сложность проекта?
Сроки зависят от объёма политик и архитектуры. Мы выделяем три варианта:
| Вариант | Срок | Что входит |
|---|---|---|
| Базовый | 3–4 дня | Движок в коде, 5–10 политик, тесты |
| Расширенный | 7–10 дней | Политики в БД, REST API, UI для редактирования, аудит |
| Интеграция с OPA | 2–3 дня | Sidecar-развёртывание, написание политик на Rego, тестирование |
Что входит в работу по внедрению ABAC?
- Анализ модели доступа и атрибутов пользователей, ресурсов, окружения.
- Проектирование и документирование политик доступа.
- Разработка движка принятия решений (или интеграция с OPA/Casbin).
- Реализация REST API для управления политиками.
- Создание системы аудита с визуализацией логов.
- Покрытие тестами (unit + integration).
- Деплой и настройка мониторинга.
- Обучение команды: как писать и отлаживать политики.
- Гарантия на код и поддержка после внедрения.
Базовый движок с хранением политик в коде — 3–4 дня. Движок с хранением политик в базе и UI для их редактирования — 7–10 дней. Добавление audit log с UI — ещё 2–3 дня. Интеграция со сторонней PDP (Open Policy Agent, Casbin) вместо самописного движка — 2–3 дня на интеграцию плюс время на написание политик на Rego или PERM.
Open Policy Agent — зрелая альтернатива самописному движку. Политики пишутся на Rego, OPA запускается как sidecar или как отдельный сервис, приложение обращается к нему через HTTP или gRPC. Это добавляет операционную сложность, но даёт версионирование политик, горячую перезагрузку и встроенный аудит.
Если у вас сложные требования к доступу — обратитесь к нам за аудитом. Оценим ваш проект бесплатно. Свяжитесь с нами, чтобы обсудить детали. Закажите реализацию ABAC под ключ.







