Роли и права в веб-приложении: типичные ошибки
Система ролей ломается не из-за нехватки таблиц, а из-за неверной границы доступа. Разбираем, почему проверка роли в интерфейсе опасна, когда нужен RBAC, как учитывать владельца ресурса и что проверять перед релизом.

Проверка вроде user.role === "admin" может открыть чужие счета, документы или настройки организации, если её оставить единственным уровнем защиты. В этой статье — практический разбор проектирования авторизации в веб-приложении: от модели разрешений до тестов, аудита и поведения под кешем. Подход не привязан к API одного фреймворка и применим к приложениям на Django 5.x, Laravel 11 и Spring Security 6.x, но конкретные имена middleware и классов нужно сверять с документацией вашей версии.
Сначала разделите роль, право и область доступа
Самая частая ошибка — считать роль готовым ответом на вопрос «что может пользователь». Роль описывает набор обязанностей, право — конкретное действие, а область доступа — над какими объектами это действие разрешено.
Например, «менеджер» — роль. invoice.read — право. «Только счета своей организации» — область. Если хранить только строку manager, система не сможет корректно ответить на вопрос, может ли конкретный менеджер открыть именно этот счёт.
Сноска. RBAC (Role-Based Access Control) — управление доступом через роли: пользователю назначают роль, а роли — набор разрешений. В чистом RBAC принадлежность к объекту и его владельцу часто требует дополнительной проверки, поэтому для бизнес-приложений одной ролевой модели обычно недостаточно.
Базовая модель разрешения
Полезно раскладывать каждое решение на четыре значения:
- кто действует — идентификатор пользователя и его организация;
- что делает — действие вроде чтения, изменения, удаления или экспорта;
- над чем действует — тип и идентификатор ресурса;
- в каком контексте — организация, проект, регион, статус объекта или другие ограничения.
Проверка должна выполняться на сервере непосредственно перед операцией. Фронтенд может скрыть кнопку, но не должен решать, разрешён ли запрос. Пользователь способен отправить тот же HTTP-запрос вручную, из старой вкладки или через скрипт.
Роль отвечает на вопрос «какую работу выполняет пользователь», но не на вопрос «какой объект он вправе изменить прямо сейчас».
Четыре подхода и цена каждого
Обычно команда начинает с простого RBAC, а затем добавляет ограничения по владельцу, организации и состоянию объекта. Важно понимать, где заканчивается каждый подход.
| Подход | Когда подходит | Что выигрываете | Чем платите |
|---|---|---|---|
| Роли без отдельных прав | Небольшой внутренний сервис с 2–3 уровнями доступа | Быстрый старт и простая схема данных | Любое новое исключение раздувает код проверками ролей |
| RBAC с каталогом прав | Продукт с несколькими командами и разными наборами функций | Разрешения можно переиспользовать и тестировать отдельно | Нужно управлять назначениями, миграциями и интерфейсом администрирования |
| RBAC плюс проверка владельца | CRM, кабинеты клиентов, проекты, документы | Роль отделена от принадлежности конкретного ресурса | Каждый запрос должен загружать или проверять контекст объекта |
| Политики и атрибуты | Сложные правила: регион, сумма, статус, время, тип договора | Правила точнее отражают бизнес-процесс | Сложнее объяснять решение, покрывать тестами и расследовать отказы |
Когда выбрать простую модель
Роли без каталога прав допустимы, если список ролей стабилен, объектов немного, а приложение не разделено на организации. Даже в таком случае проверяйте не только роль, но и состояние операции. Например, оператор может видеть заявку, но не менять уже закрытую заявку.
Если продукт развивается, переходите к именованным разрешениям: project.view, project.edit, billing.export. Названия действий должны описывать операцию, а не экран. Право reports.page быстро становится бесполезным, когда тот же отчёт появляется в API и фоновой задаче.
Когда нужны политики
Политика нужна, если ответ зависит от нескольких условий. Например: менеджер может изменить заказ своей организации, пока заказ не передан в доставку, а сумму выше установленного внутреннего порога подтверждает руководитель. Такое правило нельзя надёжно выразить только добавлением ролей «менеджер-1», «менеджер-2» и «менеджер-руководитель».
Не смешивайте проверку права и бизнес-валидацию. Право отвечает, может ли пользователь вызвать действие. Бизнес-правило — можно ли выполнить его с этим статусом, суммой или набором полей. На практике обе проверки могут вернуть отказ, но причины и аудит у них разные.
Типичные ошибки, которые находят слишком поздно
1. Доверие к данным из браузера
Скрытая кнопка «Удалить» не является защитой. То же относится к полю is_admin в JSON, параметру role в форме и идентификатору организации, который клиент прислал сам. Сервер должен брать субъект из проверенной сессии или токена, а принадлежность ресурса определять по данным на сервере.
Признак проблемы — запрос с подменённым resource_id проходит, хотя объект принадлежит другой организации. Это разновидность нарушения объектного контроля доступа: право на действие есть, но права на конкретный объект нет.
2. Проверка роли вместо проверки права
Условие «если администратор» кажется коротким, но связывает бизнес-логику с названием должности. Через месяц появится финансовый контролёр, которому нужен экспорт, но не удаление. Если проверки разбросаны по коду, команда начнёт добавлять исключения и случайно расширит доступ.
Храните матрицу «роль — право» отдельно от обработчиков. В коде проверяйте действие, а не строку роли. Для опасных операций полезно иметь отдельные права даже у администратора: удаление, массовый экспорт, изменение платёжных реквизитов и управление пользователями не должны автоматически следовать из одного общего флага.
3. Скрытый суперпользователь
Обход «если пользователь с таким-то идентификатором, разрешить всё» ускоряет разработку, но создаёт непрозрачную дыру. Такой доступ трудно отозвать, он часто не попадает в обычный аудит и переживает удаление роли.
Если аварийный доступ необходим, оформите его как отдельный режим: явное назначение, ограниченный срок, обязательное журналирование и дополнительное подтверждение. Не оставляйте постоянный обход в условии внутри контроллера.
4. Неполная проверка списка и массовых операций
Карточка документа может быть защищена, а эндпоинт списка — нет. Тогда пользователь не откроет чужой документ по прямой ссылке, но увидит его название, сумму и идентификатор в выдаче. Та же ошибка возникает в массовом удалении, импорте, экспорте и фоновых задачах.
Проверяйте границу доступа там, где формируется выборка. Для операции над набором объектов нужно убедиться, что каждый объект входит в разрешённую область, либо строить выборку сразу из доступного множества. Нельзя принимать список идентификаторов и доверять ему только потому, что каждый элемент был когда-то показан в интерфейсе.
5. Несогласованность между HTTP-методами
Команда закрывает PUT, но забывает про альтернативный путь через PATCH, импорт или внутреннюю задачу. Авторизацию нужно проверять на уровне операции и ресурса, а не только в одном маршруте. Отдельно проверьте GraphQL-резолверы, вебхуки и команды, которые запускаются очередью.
6. Кеширование ответа без учёта пользователя
Публичное кеширование страницы с персональными данными может показать результат одного пользователя другому. Даже приватный кеш опасен, если ключ учитывает URL, но не организацию и субъект. Кешируйте только после определения политики доступа и не используйте общий ключ для ответов, состав которых зависит от прав.
Как проектировать матрицу без хаоса
- Составьте инвентаризацию ресурсов. Запишите не только страницы, но и сущности, фоновые задачи, файлы, отчёты, операции импорта и экспорта. Проверка: для каждого действия есть конкретный ресурс и ожидаемый результат отказа.
- Назовите действия. Разделите чтение, создание, изменение, удаление, публикацию, экспорт и управление доступом. Проверка: два разработчика одинаково понимают, что означает каждое право.
- Опишите область. Укажите, действует ли право на всю систему, организацию, проект, собственные объекты или только на объекты в определённом статусе. Проверка: для спорного запроса можно словами объяснить, почему он разрешён или отклонён.
- Выберите безопасное значение по умолчанию. При отсутствии явного разрешения возвращайте отказ. Проверка: новая роль, новый маршрут и новый тип ресурса не получают доступ автоматически.
- Назначьте владельца матрицы. Техническая команда отвечает за реализацию, а владелец продукта — за смысл правила. Проверка: у каждого права есть описание, ответственный и сценарий использования.
- Добавьте негативные тесты. Проверяйте не только «менеджер видит свой проект», но и «менеджер не видит чужой проект», «обычный пользователь не экспортирует», «отозванное право перестаёт работать». Проверка: тесты проходят на API, UI и фоновой обработке.
Что проверять на ревью и перед релизом
На ревью ищите не только пропущенный декоратор или middleware. Формулировка замечания должна указывать на конкретный риск: «Проверяется наличие права invoice.read, но не принадлежность счёта организации пользователя» точнее, чем «добавьте авторизацию».
- Можно ли изменить идентификатор объекта и получить чужие данные?
- Одинаково ли проверяются браузер, API, мобильный клиент и фоновой обработчик?
- Что произойдёт с уже выданной сессией после отзыва роли?
- Попадают ли отказ, субъект, ресурс и причина решения в аудит без секретов и персональных данных сверх необходимого?
- Не расширяет ли новый маршрут доступ по умолчанию?
- Не возвращает ли список, поиск, сортировка или экспорт больше данных, чем карточка объекта?
Тестируйте матрицу как таблицу решений. Для каждого сочетания «субъект — действие — ресурс — контекст» должен быть ожидаемый результат. В продакшене отдельно измеряйте долю отказов по правам, количество обращений к политике на запрос и время её проверки. Резкий рост отказов после релиза часто означает не атаку, а ошибку миграции ролей или кеширования.
Где подход ломается
Одна ролевая модель плохо описывает временный доступ, делегирование, согласование по сумме и правила, зависящие от внешней системы. Не пытайтесь лечить это бесконечным добавлением ролей. Введите отдельные сущности для делегирования и согласований, задайте срок действия и журнал изменений.
Сложная политика тоже не бесплатна. Если решение нельзя объяснить администратору и воспроизвести в тесте, поддержка будет исправлять доступ вручную. Для каждого отказа сохраняйте безопасную причину вроде «нет права» или «ресурс другой организации», но не раскрывайте пользователю лишние сведения о существовании чужого объекта.
Инвалидация кеша прав — отдельная тема. Здесь важно только одно: после изменения роли заранее определите, когда старое решение перестаёт действовать, и проверьте этот сценарий отдельно.
Что делать дальше
Начните с одного критичного ресурса, например документов или платежей. Нарисуйте его матрицу, добавьте проверки владельца, негативные тесты и запись отказов в аудит. Затем прогоните запросы с подменёнными идентификаторами через все точки входа.
Для сверки терминов и границ модели используйте материалы NIST по RBAC и рекомендации OWASP по авторизации. После этого измеряйте не число ролей, а количество исключений, ручных выдач доступа и отказов после релизов: именно они показывают, что модель начинает рассыпаться.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: OWASP Authorization Cheat Sheet, NIST Role-Based Access Control
Частые вопросы
Достаточно ли скрыть кнопку или раздел в интерфейсе?
Когда RBAC становится слишком простым?
Что делать, если у меня другая версия фреймворка?
Почему пользователь видит объект в списке, но не может открыть его?
Нужно ли делать отдельное право для администратора?
Что делать, если после отзыва роли старая сессия всё ещё работает?
Нужно ли журналировать каждый отказ?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.