Запрос к API содержит идентификатор ресурса в URL. Как не допустить выдачу чужого ресурса при корректном формате запроса?
Нужно проверять не только подлинность пользователя, но и его право доступа именно к ресурсу, указанному в запросе. Сервер должен загрузить ресурс и применить объектную авторизацию по контексту пользователя; одного факта наличия идентификатора или успешной аутентификации недостаточно.
Ранние API часто ограничивались проверкой сессии или токена: если запрос исходил от известного пользователя, сервер считал его допустимым. Такой подход не учитывает, к каким конкретным объектам пользователь имеет доступ.
Проблема стала особенно заметной в API, где идентификаторы ресурсов передаются в URL. Предсказуемый или случайно полученный идентификатор сам по себе не должен давать доступ к объекту другого владельца.
Пусть пользователь с идентификатором A отправляет корректный запрос к ресурсу, принадлежащему пользователю B. Проверка формата идентификатора и наличие действующего токена пройдут успешно, но выдача объекта будет нарушением авторизации.
Опасная реализация ищет ресурс только по его идентификатору. Правильная проверка должна учитывать как минимум субъект запроса, сам ресурс и действующее правило доступа: например, владение, членство в организации или назначенную роль.
Неверное решение приводит к горизонтальному повышению привилегий: пользователь получает доступ к данным другого пользователя без изменения своей роли. Утечка может возникнуть даже при полностью исправной аутентификации.
Сервер должен выполнять проверку в контексте текущего пользователя и конкретного объекта. Логика обычно состоит из следующих этапов:
Проверка должна находиться на сервере, а не в клиентском интерфейсе. Скрытие ссылок, фильтрация кнопок и проверка параметров в браузере не являются защитой: клиент может сформировать запрос самостоятельно.
Для чтения часто допустимо возвращать одинаковый ответ об отсутствии ресурса и отсутствии доступа. Это уменьшает раскрытие факта существования чужого объекта, хотя точный выбор ответа зависит от требований API и модели угроз.
Проверку нельзя делать только при чтении. Те же правила должны применяться к изменению, удалению, скачиванию, запуску операций и вложенным ресурсам. Особенно важно повторно проверять доступ при фоновой обработке, если задача была создана одним пользователем, а выполняется позже.
Критерий доступа лучше выражать через бизнес-инварианты, а не через сравнение одного поля, если права сложнее владения. Например, доступ к документу может зависеть от организации, роли, статуса документа и явного разрешения.
Идентификаторы, которые трудно угадывать, могут снизить риск массового перебора, но не заменяют авторизацию. Ограничение частоты запросов и журналирование помогают обнаруживать злоупотребления, однако также не исправляют отсутствие проверки прав.
В системе совместной работы пользователь мог открыть документ по идентификатору из URL. Сервис проверял действительность токена и существование документа, но не проверял, состоит ли пользователь в организации-владельце. В результате любой аутентифицированный клиент, узнавший идентификатор, мог прочитать чужой документ.
Рассматривались два варианта. Первый — фильтровать документы только на уровне клиентского интерфейса; это почти не защищает API и не подходит для прямых запросов. Второй — добавлять проверку прав в каждый обработчик вручную; это лучше защищает данные, но создаёт риск расхождения правил и пропуска проверки в новом endpoint.
Выбрали централизованную серверную политику доступа, которую вызывают все операции с документом, а бизнес-правила передают ей текущего пользователя и ресурс. Для запросов к коллекциям дополнительно применили серверную фильтрацию по области доступа, чтобы запрещённые объекты не попадали в результат вообще.
Такое решение уменьшило вероятность пропуска проверки при расширении API и сделало правила доступа единообразными. При этом политика стала отдельной точкой сложности: её нужно тестировать на положительные и отрицательные сценарии, включая доступ между организациями, удалённые учётные записи и изменение членства.
1. Достаточно ли использовать непредсказуемые идентификаторы вместо проверки прав?
Нет. Непредсказуемый идентификатор усложняет перебор, но не выражает разрешение доступа. Идентификатор может попасть в журнал, сообщение, ссылку или ответ другого сервиса; если сервер не проверяет права, любой такой случай превращается в утечку.
2. Где проверять доступ: до или после загрузки ресурса?
Для проверки, зависящей от свойств ресурса, серверу сначала нужно получить сам объект или необходимые для политики атрибуты. Затем он проверяет право доступа и не возвращает содержимое до успешного результата. В списковых запросах предпочтительнее сразу формировать выборку с ограничением по области доступа, чтобы запрещённые объекты не извлекались без необходимости.
3. Нужно ли повторять проверку в каждом микросервисе?
Каждый сервис, который непосредственно возвращает или изменяет защищённые данные, должен сам обеспечить авторизацию; доверять только проверке во внешнем шлюзе опасно. Шлюз может проверять токен и общие ограничения, но внутренний сервис должен учитывать свои ресурсы и бизнес-правила. Иначе новый маршрут, внутренний вызов или ошибка маршрутизации могут обойти защиту.