Аутентификация и авторизация: как тестировщику проверять права доступа
Многие серьёзные security-баги начинаются с простой путаницы: система знает, кто ты, но забывает проверить, принадлежит ли тебе конкретный объект.
- Нет токена: ожидаем 401
- Неверный токен: ожидаем 401
- Правильный пользователь, не та роль: 403 или нет действия
- Правильная роль, чужой объект: 403 или 404
Аутентификация отвечает “кто ты?”
Аутентификация — это вход, пароль, токен, сессия и срок действия. Она подтверждает личность, но не решает, какие данные пользователь может читать или менять.
Базовые проверки: правильные данные, неверный пароль, регистр email, отсутствие токена, истёкший токен и logout.
Авторизация отвечает “что тебе можно?”
Авторизация проверяет роль и владение объектом. Пользователь может быть залогинен и всё равно не иметь права открыть чужой заказ, счёт, документ или карточку клиента.
Здесь часто ломается API: id легко угадать, а скрытая кнопка на фронтенде не защищает backend.
Проверка владения объектом
Создай или найди двух пользователей. Открой объект владельцем, запомни id, затем запроси тот же id токеном второго пользователя. Обычно ожидается 403 или 404.
Почему иногда лучше 404
Для приватных объектов 404 скрывает сам факт существования. 403 говорит, что объект есть, но тебе нельзя. Как правильно — решают требования продукта.
Проверка ролей
Роли нужно проверять и в UI, и в API. Если admin-only кнопка скрыта, всё равно отправь запрос с токеном обычного пользователя.
Скрытая кнопка — это удобство. Отклонённый запрос — это безопасность.
Типичные access-баги
Частые дефекты: нет фильтра по владельцу, роль проверяется только на фронтенде, токен работает после logout, endpoint по-разному отвечает на существующий и несуществующий приватный объект.
В багрепорте про доступ укажи оба аккаунта, контекст токенов и id объекта. Без этого разработчик не воспроизведёт границу прав.
Матрица actor × action × object
Список ролей недостаточен. Построй строки из субъекта, действия и ресурса: manager A читает своего клиента, manager A меняет клиента B, обычный user вызывает admin endpoint, бывший сотрудник использует старый token. Для каждого сочетания нужен expected: allow, deny или скрыть существование.
Проверяй не только GET. BOLA часто закрыта на чтение, но остаётся в PATCH, DELETE, bulk endpoint, export или вложенном object id в JSON. UUID усложняет угадывание, но не заменяет серверную проверку права.
Actor Action Object Expected\nmanager A GET client A 200\nmanager A GET client B 403/404 по контракту\nmanager A PATCH client B 403/404, данные не меняются\nviewer DELETE client A 403\nno token GET client A 401 + WWW-Authenticate
Жизненный цикл сессии и токена
Проверь выдачу, rotation после login или повышения прав, expiry, refresh, logout, отзыв, смену пароля и параллельные устройства. Старый access token иногда живёт до короткого expiry по принятой архитектуре; тогда logout обязан отозвать refresh token и UI не должен обещать больше, чем гарантирует сервер.
- Session id не остаётся тем же до и после login — иначе возможна session fixation.
- Cookie имеет Secure, HttpOnly и подходящий SameSite; token не попадает в URL и логи.
- Истёкший, испорченный и подписанный не тем ключом token отклоняются.
- После смены роли permission обновляется в согласованный срок, а кэш не оставляет старое право.
- Refresh reuse, logout и смена пароля ведут себя по документированной политике.
BOLA, BFLA и доступ к отдельным полям
BOLA — пользователь имеет доступ к функции, но backend не проверяет право на конкретный object id. BFLA — пользователь вызывает функцию не своей роли, например обычный manager открывает admin export. Broken Object Property Level Authorization проявляется, когда разрешённый endpoint принимает или возвращает поля, которые этой роли недоступны.
Для CRM это три разные проверки: запросить чужого клиента, вызвать admin-only массовый export и отправить в PATCH поле owner_id или is_vip, которого нет в форме. Одной скрытой кнопкой ни один из этих рисков не закрывается.
Разбор: три разных нарушения прав в одной CRM
Один сценарий с чужим клиентом не доказывает, что доступ защищён. Возьми два аккаунта менеджеров одного tenant, админа и зрителя только на чтение. Тестовые клиенты должны иметь известных владельцев и синтетические данные. Затем проверь три независимые границы: объект, функцию и отдельное поле.
- Определить роли и владельцев объектов.
- Проверить доступ к чужому id.
- Вызвать функцию чужой роли напрямую.
- Попробовать записать запрещённое поле.
BOLA: функция разрешена, объект чужой
Менеджер B имеет право вызывать GET /clients/{id} для своих клиентов. Передай id клиента A с токеном B и сравни результат с контрактом: 403 или 404, но без данных A. Повтори для PATCH и DELETE в тестовой среде, а также для вложенного id в теле запроса. Непредсказуемый UUID не является проверкой права: id может утечь через экспорт или ссылку.
BFLA: сама функция недоступна роли
Массовый экспорт или назначение менеджера может быть доступно только администратору. Скрытая кнопка у B ничего не доказывает: отправь прямой запрос на admin endpoint его токеном. Проверь не только отказ, но и отсутствие побочного эффекта: не должен появиться файл экспорта, смениться владелец клиента или уйти событие в очередь. Сравни со штатным успешным действием админа, чтобы исключить сломанный endpoint.
Property-level: функция доступна, поле нет
Менеджеру разрешено менять телефон своего клиента, но не `owner_id` или `is_vip`. Добавь эти поля в PATCH body через Postman, даже если форма их не показывает. После запроса перечитай объект админом и проверь, что запрещённые поля не изменились; возврат 200 с молчаливым игнорированием может быть допустимым только если так записано в контракте. Для чтения проверь, не возвращает ли API скрытые финансовые или персональные поля лишней роли.
Учти tenant и жизненный цикл прав
В multi-tenant CRM два клиента с одинаковой ролью могут принадлежать разным организациям; нужен отдельный cross-tenant сценарий. После отзыва роли или переназначения клиента повтори старый токен и проверь согласованное время обновления прав в кеше. Сессия после logout, 401 против 403 и маскировка 404 проверяются по политике продукта, а не по личному предпочтению тестировщика.
Actor Request Expected\nmanager B GET /clients/A deny; no A data\nmanager B PATCH /clients/A deny; no write\nmanager B POST /admin/clients/export deny; no file\nmanager A PATCH /clients/A {is_vip:true} deny/ignore per contract\nviewer GET /clients/A allow only safe fields\nno token GET /clients/A 401
Что считать доказательством?
Для каждого запрета сохрани роль, id, метод, статус, очищенный от PII ответ и проверку отсутствия побочного эффекта. Три нарушения оформляй отдельно, потому что их причины и исправления различны. Если найден реальный доступ к чужим данным, эскалируй в приватном канале безопасности.
Второй кейс: проверка доступа после смены владельца
CRM переназначает клиента от A к B. Оба менеджера были авторизованы заранее, а разрешения кешируются. В момент смены владельца доступ должен перейти к B в согласованный срок. Здесь мало проверить только свежий логин: старый токен A и старые ссылки могут открыть данные после отзыва права.
Определи политику времени
Спроси, считается ли отзыв права мгновенным или допускается короткая задержка репликации и кеша. Для чувствительных данных “до следующего входа” может быть неприемлемо, но QA не должен сам выбирать TTL. Запиши максимальное окно и источник истины: owner_id в базе, permission service или claims в токене. Затем подготовь тест с контролируемым временем и двумя активными сессиями.
Проверь право до и после
До переназначения A должен читать и менять клиента, B — получать отказ. После переназначения наоборот. Проверяй GET, PATCH, список клиентов, экспорт и вложенные задачи: разные endpoint могут использовать разные способы фильтрации. Прямой API-запрос старым токеном A важнее, чем скрытие карточки в UI. При отказе не должно происходить частичного изменения.
Проверь гонку в момент смены
Запрос PATCH от A, запущенный одновременно с reassignment, требует определения порядка: он мог начаться до изменения owner_id, а записаться после него. Если контракт не определяет линейную точку принятия решения, зафиксируй вопрос команде. Для проверки сохраняй timestamp, version объекта и correlation id обоих запросов. Не объявляй гонку багом только потому, что один раз порядок оказался другим.
Не забудь отдельные поля и историю
Старый менеджер может потерять доступ к текущей карточке, но всё ещё видеть её в истории активности, поиске или выгрузке. Менеджер B может получить карточку, но не связанную сделку или задачу из-за старого owner_id. Составь список связанных ресурсов и ожидаемых прав: иногда аудит изменений остаётся доступен прежнему менеджеру по бизнес-правилу, иногда нет. Это нужно уточнить, а не предполагать.
Оцени результат без утечки данных
Оба аккаунта и клиент должны быть синтетическими. Для каждого шага фиксируй роль, token age, время смены, ответ API и отсутствие записи после отказа. Не прикладывай полные карточки реальных клиентов к общему тикету. Если stale access подтверждён, укажи точное окно, методы и объекты, а не только “кеш прав иногда устаревает”. Это ускоряет исправление и корректную оценку риска.
Time A GET/PATCH B GET/PATCH\nbefore reassignment allow deny\nafter agreed TTL deny allow\nold token A deny —\nsearch/export A no client A —\nlinked tasks according to ownership policy
Что считать достаточным доказательством?
Не просто смену owner_id в базе, а согласованное поведение всех путей чтения и записи после границы времени. Если старый токен сохраняет доступ дольше допуска, это отдельная security-находка с измеренной длительностью, воспроизведённая на тестовых данных.
Частые вопросы о доступе
Что правильнее для чужого приватного объекта: 403 или 404?
Оба варианта возможны по контракту. 404 скрывает сам факт существования объекта, 403 явно сообщает об отказе. Главное — единая политика, отсутствие утечки и невозможность действия.
Если id — UUID, BOLA невозможна?
Нет. UUID снижает возможность перебора, но id может попасть в ссылку, лог, экспорт или другой ответ. Сервер обязан проверять право на каждый объект независимо от формата id.
Достаточно ли проверить роли через UI?
Нет. Скрытый элемент улучшает UX, но запрос можно отправить напрямую. Авторизация должна действовать на сервере для каждого метода и объекта.
Проверка доступа маленькая по шагам и большая по последствиям: иногда один изменённый id показывает, защищает ли backend пользовательские данные на самом деле.