Права на ресурс
API уже распознаёт Alice как пользователя u17. Теперь нужно решить, что она вправе сделать. Подтверждённая личность не является разрешением редактировать любой курс, читать чужое задание или переносить запись в другую тему. Авторизация связывает конкретное действие с конкретным ресурсом и текущими условиями.
В этом уроке ограничим редактора областью frontend. Результат — обновление c001 принимается при остальных подходящих условиях, а изменение c003 отклоняется. Все сценарии независимы и начинают прежние четыре курса. Мы не реализуем серверную библиотеку доступа: текст задаёт требуемую проверку и показывает ожидаемое отсутствие изменения при отказе.
Роль и область
У Alice роль editor и список topics, содержащий frontend. В снимке s1 этой области соответствуют c001 JavaScript и c002 HTML/CSS. Курс c003 имеет quality, c004 — tools. Публичное чтение всех четырёх сохраняется, но право изменения не следует из того, что человек видит карточку.
Предметную политику можно описать небольшой таблицей:
| Действие | Alice |
|---|---|
| Читать публичный курс | Разрешено |
| Изменить c001 или c002 | Разрешено после остальных проверок |
| Изменить c003 или c004 | Запрещено |
| Создать курс в frontend | Разрешено |
| Создать курс в backend | Запрещено |
Это правила нашего сервиса, а не особое значение стандартного слова editor. В другом продукте роль может означать иные права. Поэтому алгоритм должен обращаться к объявленной политике, а не считать любое непустое поле role достаточным условием.
Разрешённое изменение
Alice отправляет PUT курса c001 через Bearer, чтобы в этом уроке отдельно увидеть право без Cookie-CSRF. Токен условный, действительность предполагается по предыдущему договору. Путь, документ и версия сохраняют прежние правила:
PUT /api/courses/c001 HTTP/1.1
Host: catalog.example
Authorization: Bearer tok-alice
Content-Type: application/json
Accept: application/json, application/problem+json
If-Match: "c001-json-r1"
{"title":"JavaScript для веба","topic":"frontend","lessons":40}
Сервис распознаёт subject, получает текущий курс и проверяет его тему. Она frontend, поэтому Alice находится в разрешённой области. Если вход корректен и версия совпадает, замена принимается. Ответ PUT содержит обновлённый объект без нового ETag, как было определено ранее для преобразуемого входа.
Перечисленные проверки не взаимозаменяемы. Валидный токен не устраняет необходимость If-Match, а совпавшая версия не создаёт права. Синтаксически правильный JSON также может быть недопустим для данного субъекта. Такой разбор позволяет объяснить отказ предметно, не объявляя любую проблему «неправильным запросом».
Тот же редактор, другой курс
В отдельном сценарии Alice пытается изменить c003, оставляя его направление quality. Документ может быть корректным и включать подходящий валидатор. Однако право на этот ресурс отсутствует. Ожидаемый ответ:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Cache-Control: no-store
{"type":"https://catalog.example/problems/operation-forbidden","title":"Операция запрещена","status":403,"detail":"У вас нет права изменять этот курс."}
Курс остаётся прежним: 24 урока, публичное название и направление quality. Повторный вход тем же пользователем не обязательно исправит отказ. Клиенту не нужно бесконечно перенаправлять Alice к паролю: личность уже известна, недостаёт разрешения.
Наш публичный ресурс раскрывает существование курса, поэтому 403 здесь разумно объясняет запрет операции. Для закрытого файла другого владельца мы выбираем 404, чтобы не сообщать его существование. Это прикладная политика сокрытия, которую нужно описать и применять последовательно. Нельзя делать универсальный вывод о базе только по одному 404.
Перенос требует двух оснований
Представим более тонкий запрос: Alice меняет c001 и одновременно задаёт topic quality. Старый курс находится в её области, но новое состояние выходит за неё. Если проверять только прежнюю тему, пользователь сможет переносить записи в недоступный раздел.
Наш договор требует разрешение и на текущую, и на новую тему. PUT с topic quality отвергается как запрещённая операция, хотя quality является допустимым значением общего enum. Валидация говорит, что направление существует; авторизация — что этот пользователь вправе его применить. Эти два результата не противоречат друг другу.
Для создания проверяется новая тема, поскольку старой записи нет. Alice может создать ещё один frontend-курс, но не курс backend из раннего общего примера. Уроки 1–16 предполагали редактора с установленными правами без конкретной области; теперь мы уточняем субъекта, а не меняем семантику POST.
При создании с Idempotency-Key область дедупликации включает subject. Два разных редактора с одинаковой строкой ключа не получают чужой результат. При повторе старого результата право чтения этого результата также нужно оценить по политике: пригодный ключ не становится вечным обходом отозванного доступа.
Разрешение проверяется на сервере
Интерфейс может скрыть кнопку редактирования c003, чтобы Alice не тратила время. Но запрет должен существовать и в обработчике API. Пользователь способен отправить запрос другим клиентом, изменить адрес или вызвать сохранённую операцию после изменения роли.
Идентификатор c001 в пути не является секретом. Даже длинный случайный id не заменяет проверку ресурса: если кто-то узнал его из ссылки или журнала, право не возникает. Для файла и задания сервер связывает ресурс с владельцем, а не доверяет присланному полю owner.
Не следует принимать роль или разрешённые темы из JSON операции курса. Эти свойства относятся к доверенной модели доступа, а не к редактируемому представлению. Вход {"role":"admin"} вообще не входит в разрешённый документ и не должен управлять политикой. Принципы проверки доступа OWASP.
Список не становится разрешением на запись
Фильтр topic=frontend помогает Alice выбрать подходящие публичные курсы, но не является доказательством права изменения. Клиент способен открыть прямой адрес c003 после любой выборки. Обработчик PUT проверяет текущий ресурс независимо от того, каким экраном человек пришёл к нему.
И обратное: скрытие запрещённого курса в редакторском списке не меняет публичный каталог автоматически. У нас чтение c003 разрешено всем, запись — только подходящему редактору. Если продукт позже потребует закрытые курсы, нужно отдельно определить их чтение, фильтрацию и кеширование. Нельзя случайно применять один и тот же фильтр доступа к обеим задачам, не объяснив пользователю новый смысл списка.
Так политика остаётся связанной с действием и ресурсом, а не с последовательностью экранов. Серверный запрос после закладки или из другого разрешённого клиента проходит те же границы.
Права меняются со временем
Alice могла открыть форму, когда была editor, а затем доступ отозвали. Сохранённый экран не подтверждает нынешние права. Новый PUT должен учитывать актуальный субъект и политику. Если сервер закешировал разрешение без подходящего срока и отзыва, документация немедленного ограничения окажется ложной.
Проверка текущего ресурса и принятие изменения должны находиться в согласованной границе. Если тема курса изменится между отдельной проверкой доступа и записью, обработчик не должен применять документ к неожиданному состоянию. Условная версия помогает, но её атомарность должна сочетаться с соответствующей политикой хранения.
В журнале записывают subject, действие, ресурс и исход, избегая секретных токенов. Это помогает расследовать, почему c003 не изменился и каким основанием была принята c001. Журнал не должен заменять отказ: система обязана предотвращать запрещённое действие, а не только заметить его после сохранения.
Теперь сравните три варианта: неизвестная личность, известная Alice без области quality и Alice с разрешённым frontend, но устаревшей версией. Они дают разные причины и разные действия клиента. Следующий урок добавит origin браузера: он ограничивает доступ к ответу и пересекается с сессией, но не заменяет эти предметные разрешения.