Webrium
Подписаться

Аутентификация и авторизация: где проходит граница

Как работают проверка личности и прав доступа и почему системы возвращают разные ошибки

Две части системы доступа: проверка личности и проверка прав, разделённые границейАутентификацияАвторизация

Кто пришёл и что ему можно

Представьте, что вы пришли в офис. На входе охранник проверяет ваш пропуск — это первое действие. Пропуск подтверждает, что вы тот, за кого себя выдаёте, но ничего не говорит о том, куда вам можно зайти. Кабинеты, шкафы с документами, серверные комнаты — у каждой двери свой список разрешённых лиц, и его проверяют каждый раз, когда вы подходите. Если пропуск недействителен, вас не пустят никуда. Если пропуск в порядке, но в списке у двери вас нет — тоже не пустят. Две разные проверки, два разных отказа.

В системах работает то же самое. Одна проверка устанавливает, кто пришёл, другая решает, что этому пришедшему разрешено. Первую называют аутентификацией, вторую — авторизацией. Если вам нужен только ответ на вопрос «в чём разница», дальше можно не читать: всё, что нужно, уже сказано. Если хотите понять, почему системы ломаются именно так, а не иначе — продолжаем.

Три слова и два шага

В русскоязычных интерфейсах словом «Авторизация» подписывают форму входа. Поэтому в разговоре оно означает «войти», а не «получить доступ». В технических разборах тему обычно делят на три шага: идентификация (предъявлен идентификатор — логин, почта, номер), аутентификация (предъявленное подтверждено паролем, кодом, ключом), авторизация (определены права). Триада удобна для объяснения, но на практике шагов остаётся два: идентификация и аутентификация сливаются в одну проверку подлинности.

Порядок «сначала личность, потом права» кажется очевидным, но не везде обязателен. Если правило доступа не зависит от того, кто субъект, проверка подлинности может вообще не понадобиться. Например, публичный ресурс отвечает анонимному запросу, а подписанный адрес или ссылка-приглашение дают право на операцию, не устанавливая личность. Спецификация HTTP прямо допускает отказ в доступе по причинам, не связанным с учётными данными.

Что проверяет каждый запрос

Вход происходит разово: пользователь предъявляет фактор (пароль, код, ключ), система выпускает сессию или токен. Но проверка предъявленного удостоверения повторяется на каждом запросе. Сервер каждый раз проверяет подпись токена либо находит сессию и убеждается, что она жива и не отозвана. В схемах уровня HTTP-аутентификации учётные данные посылаются с каждым запросом, потому что HTTP не хранит состояния между запросами.

Решение о правах принимается на каждом запросе и для каждого объекта отдельно. Если пропустить эту проверку при живой сессии, система отдаст чужой объект по идентификатору из адреса. Например, если пользователь запросил запись с ID 123, а проверка владельца не сработала, он получит чужую запись — даже если сессия у него действительна. Разовый вход не заменяет ни проверку предъявленного удостоверения, ни проверку прав: вход подтверждает подлинность один раз, проверка удостоверения повторяет это подтверждение на каждом запросе, а право на конкретный объект устанавливается заново — чтением самого объекта.

Два кода, два вопроса

Код 401 означает, что запрос не выполнен, потому что не содержит действительных учётных данных для этого ресурса. Его историческое название Unauthorized относится именно к непройденной проверке подлинности, а не к нехватке прав. Ответ с этим кодом обязан нести заголовок WWW-Authenticate хотя бы с одним вызовом, применимым к запрошенному ресурсу. Вызов — это схема аутентификации вместе с её параметрами, и вызовов в заголовке может быть несколько.

Код 403 означает, что запрос понят, но доступ запрещён. Клиенту не следует автоматически повторять запрос с теми же учётными данными, хотя он может повторить его с другими. Запрет бывает вызван причинами, не связанными с учётными данными: режим только чтения, ограничение по адресу, отключённая возможность, состояние самого объекта. Поэтому 403 не синоним «этому пользователю не хватило прав», и сервер не обязан присылать вызов на аутентификацию.

Отсюда обычная жалоба: сервер отвечает «запрещено» там, где ждали требования войти заново. Разбирать её надо в одну сторону. Если учётные данные были предъявлены, код 403 означает, что сервер счёл их недостаточными для этого запроса, — но недостаточны они не обязательно из-за нехватки прав: причина запрета бывает и той, что к учётным данным не сводится. Обратный вывод неверен тем более: тот же код приходит и на запрос вообще без учётных данных. Поэтому по одному коду не видно, изменит ли что-нибудь повторный вход, — а там, где причина не в учётных данных, не изменит. Например, объект переведён в режим только чтения: учётные данные сервер принял, запрещает состояние объекта, и вход заново этого состояния не меняет.

Граница между кодами проведена так: действительных учётных данных нет — 401 и обязательный вызов в заголовке; учётные данные действительны, но их недостаточно для доступа — 403. В схеме с токеном-предъявителем это выражено явно: истёкший, отозванный или негодный токен — ошибка invalid_token и код 401; выданных прав токена не хватает на операцию — insufficient_scope и код 403. Какой из двух кодов вернёт маршрут, решается вместе с формой самого маршрута, а не после неё: это часть проектирования API.

Скрытая на клиенте кнопка не является ограничением доступа. Клиент формирует запрос сам и может отправить его напрямую, поэтому решение о допуске принимается только на сервере, а проверка на клиенте нужна ровно для того, чтобы не показывать заведомо недоступное, — то же самое и с проверкой форм. Сервер, желающий скрыть существование запрещённого ресурса, вправе ответить 404 вместо 403, но тогда скрытие нужно выдерживать на всех операциях с объектом. Иначе разница в ответах соседних запросов всё равно выдаст, что объект есть.

Внешний вход и делегирование

OAuth — это рамочная спецификация делегирования доступа. Она описывает, как приложение получает ограниченный доступ к ресурсам от имени пользователя. Слово «авторизация» в OAuth означает согласие владельца ресурса выдать приложению доступ, а не проверку прав на стороне ресурсного сервера.

Как приложение узнаёт личность пользователя, OAuth не описывает. Поверх него надстраивают отдельный слой идентификации — он и вводит ID-токен. Токен доступа предназначен ресурсному серверу, и клиенту запрещено разбирать его содержимое: формат в любой момент может смениться на непрозрачный. ID-токен предназначен клиенту. Поэтому ID-токен нельзя предъявлять API как пропуск, а токен доступа нельзя считать доказательством личности пользователя. Что у токена внутри и почему отзыв выданного — отдельная задача, разобрано в материале JWT: что это на самом деле и где он не подходит; где держать выданный токен на клиенте — в материале Токен в браузере: где его хранить.

Область действия токена — не права пользователя. Она ограничивает, что приложению позволено делать от имени пользователя, и не способна выдать больше, чем есть у самого пользователя. Сервер авторизации вправе выдать меньшую область, чем запросили, и обязан сообщить, какую выдал. Итоговое разрешение — пересечение прав субъекта и выданной области.

Роли и владение

Ролевая модель хорошо выражает класс операции: администратор может всё, редактор — редактировать, гость — только смотреть. Но правило «только свои записи» ролевая модель выражает лишь частично — и то когда роль назначается не глобально, а в области видимости объекта: владелец в пределах проекта, организации, пространства.

Решение о владении нельзя вычислить по имени маршрута и роли. Для него нужно прочитать сам объект. Это верно при любой модели прав. Например, если маршрут называется /projects/{id}/tasks/{task_id}, а роль пользователя — editor, система не может знать, является ли пользователь владельцем этой задачи, пока не прочитает саму задачу.

Отказ по умолчанию: новый маршрут закрыт, пока для него явно не описано разрешение. Обратная схема — закрывать перечисленное — даёт дыру при каждом добавлении обработчика. Причём не видно её ни в диффе, ни в ревью: обработчик добавлен, а список запретов не трогали — значит, формально ничего и не изменилось.

В списках условие владельца должно попадать в сам запрос к данным, а не отсеивать выборку после получения. Иначе утечка идёт через счётчики, номера страниц и агрегаты: существование чужих объектов видно, даже когда сами объекты не отдаются.

Тупик: форма работает, задача не закрыта

Первым делом обычно заводят две роли и подключают вход через стороннего поставщика идентификации. Форма входа работает, задача выглядит закрытой. Но закрыт только первый вопрос: система теперь знает, кто пришёл. Однако по-прежнему отдаёт любой объект по идентификатору из адреса каждому, у кого живая сессия. Следующий добавленный обработчик снова открыт, если закрывают перечисленное, а не всё подряд.

Позиция редакции

Подлинность и грубый класс операции проверяются одним слоем на входе, а решение «эта запись твоя» принимается там, где объект прочитан. Вынести его выше по стеку без повторного чтения объекта нельзя. Эта позиция неверна, если все объекты в системе общие и права зависят только от роли субъекта, а не от данных. Например, во внутренней админке со справочниками централизованная проверка на входе закрывает всё, а вынос решения к объекту добавляет код и места для ошибки, ничего не давая.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Разделение подходов: проектирование от экранов и от структуры данныхЭкраны интерфейсаСтруктура данных
Предыстория

REST API: что решить до первого маршрута

Как спроектировать API, чтобы не переделывать его через месяц: структура данных, коды ошибок и правила эволюции.

Схема стыка JWT: проверенная подпись и проблема отзыва токенаПодпись проверенаОтзыв невозможен
Углубление

JWT: что это на самом деле и где он не подходит

JWT за пределами очевидного: как отозвать токен без компромиссов и где он всё-таки не нужен.

Две части валидации: клиентская для удобства, серверная для безопасности, пересекающие границу системыУдобствоБезопасность
Усиление

Валидация форм: на клиенте, на сервере или везде

Как двойная валидация убирает ложные срабатывания и не пропускает реальные угрозы — без лишних запросов к серверу.

Две угрозы по разные стороны: одна закрывается хранилищем, другая — серверомчужой скриптчужой сайт
Практика

Токен в браузере: где его хранить

Как выбрать хранилище для токена в браузере — и почему удобство здесь не главное.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

↑↓ выбрать · Enter открыть · Esc закрыть