fastapi/docs/ru/docs/advanced/security/oauth2-scopes.md

21 KiB
Raw Blame History

OAuth2 scopes

Вы можете использовать OAuth2 scopes напрямую с FastAPI — они интегрированы и работают бесшовно.

Это позволит вам иметь более детальную систему разрешений по стандарту OAuth2, интегрированную в ваше OpenAPIприложение (и документацию API).

OAuth2 со scopes — это механизм, который используют многие крупные провайдеры аутентификации: Facebook, Google, GitHub, Microsoft, X (Twitter) и т.д. Они применяют его, чтобы предоставлять конкретные разрешения пользователям и приложениям.

Каждый раз, когда вы «входите через» Facebook, Google, GitHub, Microsoft, X (Twitter), это приложение использует OAuth2 со scopes.

В этом разделе вы увидите, как управлять аутентификацией и авторизацией с теми же OAuth2 scopes в вашем приложении на FastAPI.

/// warning | Предупреждение

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

Вам не обязательно нужны OAuth2 scopes — аутентификацию и авторизацию можно реализовать любым нужным вам способом.

Но OAuth2 со scopes можно красиво интегрировать в ваш API (через OpenAPI) и документацию API.

Так или иначе, вы все равно будете применять эти scopes или какие-то другие требования безопасности/авторизации, как вам нужно, в вашем коде.

Во многих случаях OAuth2 со scopes может быть избыточным.

Но если вы знаете, что это нужно, или вам просто интересно — продолжайте чтение.

///

OAuth2 scopes и OpenAPI

Спецификация OAuth2 определяет «scopes» как список строк, разделённых пробелами.

Содержимое каждой такой строки может иметь любой формат, но не должно содержать пробелов.

Эти scopes представляют «разрешения».

В OpenAPI (например, в документации API) можно определить «схемы безопасности» (security schemes).

Когда одна из таких схем безопасности использует OAuth2, вы также можете объявлять и использовать scopes.

Каждый «scope» — это просто строка (без пробелов).

Обычно они используются для объявления конкретных разрешений безопасности, например:

  • users:read или users:write — распространённые примеры.
  • instagram_basic используется Facebook / Instagram.
  • https://www.googleapis.com/auth/drive используется Google.

/// info | Информация

В OAuth2 «scope» — это просто строка, объявляющая требуемое конкретное разрешение.

Неважно, есть ли там другие символы, такие как :, или это URL.

Эти детали зависят от реализации.

Для OAuth2 это просто строки.

///

Взгляд издалека

Сначала быстро посмотрим, что изменилось по сравнению с примерами из основного раздела Учебник - Руководство пользователяOAuth2 с паролем (и хешированием), Bearer с JWT-токенами. Теперь — с использованием OAuth2 scopes:

{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}

Теперь рассмотрим эти изменения шаг за шагом.

OAuth2 схема безопасности

Первое изменение — мы объявляем схему безопасности OAuth2 с двумя доступными scopes: me и items.

Параметр scopes получает dict, где каждый scope — это ключ, а описание — значение:

{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}

Так как теперь мы объявляем эти scopes, они появятся в документации API при входе/авторизации.

И вы сможете выбрать, какие scopes вы хотите выдать доступ: me и items.

Это тот же механизм, когда вы даёте разрешения при входе через Facebook, Google, GitHub и т.д.:

JWT-токены со scopes

Теперь измените операцию пути, выдающую токен, чтобы возвращать запрошенные scopes.

Мы всё ещё используем тот же OAuth2PasswordRequestForm. Он включает свойство scopes с list из str — каждый scope, полученный в запросе.

И мы возвращаем scopes как часть JWTтокена.

/// danger | Опасность

Для простоты здесь мы просто добавляем полученные scopes прямо в токен.

Но в вашем приложении, в целях безопасности, следует убедиться, что вы добавляете только те scopes, которые пользователь действительно может иметь, или те, которые вы заранее определили.

///

{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}

Объявление scopes в обработчиках путей и зависимостях

Теперь объявим, что операция пути для /users/me/items/ требует scope items.

Для этого импортируем и используем Security из fastapi.

Вы можете использовать Security для объявления зависимостей (как Depends), но Security также принимает параметр scopes со списком scopes (строк).

В этом случае мы передаём функцию‑зависимость get_current_active_user в Security (точно так же, как сделали бы с Depends).

Но мы также передаём list scopes — в данном случае только один scope: items (их могло быть больше).

И функция‑зависимость get_current_active_user тоже может объявлять подзависимости не только через Depends, но и через Security, объявляя свою подзависимость (get_current_user) и дополнительные требования по scopes.

В данном случае требуется scope me (их также могло быть больше одного).

/// note | Примечание

Вам не обязательно добавлять разные scopes в разных местах.

Мы делаем это здесь, чтобы показать, как FastAPI обрабатывает scopes, объявленные на разных уровнях.

///

{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}

/// info | Технические детали

Security на самом деле является подклассом Depends и имеет всего один дополнительный параметр, который мы рассмотрим позже.

Но используя Security вместо Depends, FastAPI будет знать, что можно объявлять security scopes, использовать их внутри и документировать API в OpenAPI.

Однако когда вы импортируете Query, Path, Depends, Security и другие из fastapi, это на самом деле функции, возвращающие специальные классы.

///

Использование SecurityScopes

Теперь обновим зависимость get_current_user.

Именно её используют зависимости выше.

Здесь мы используем ту же схему OAuth2, созданную ранее, объявляя её как зависимость: oauth2_scheme.

Поскольку у этой функции‑зависимости нет собственных требований по scopes, мы можем использовать Depends с oauth2_scheme — нам не нужно использовать Security, если не требуется указывать security scopes.

Мы также объявляем специальный параметр типа SecurityScopes, импортированный из fastapi.security.

Класс SecurityScopes похож на Request (через Request мы получали сам объект запроса).

{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}

Использование scopes

Параметр security_scopes будет типа SecurityScopes.

У него есть свойство scopes со списком, содержащим все scopes, требуемые им самим и всеми зависимостями, использующими его как подзависимость. То есть всеми «зависящими»… это может звучать запутанно, ниже есть дополнительное объяснение.

Объект security_scopes (класс SecurityScopes) также предоставляет атрибут scope_str — это одна строка с этими scopes, разделёнными пробелами (мы будем её использовать).

Мы создаём HTTPException, который можем переиспользовать (raise) в нескольких местах.

В этом исключении мы включаем требуемые scopes (если есть) в виде строки, разделённой пробелами (используя scope_str). Эту строку со scopes мы помещаем в HTTPзаголовок WWW-Authenticate (это часть спецификации).

{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}

Проверка username и формата данных

Мы проверяем, что получили username, и извлекаем scopes.

Затем валидируем эти данные с помощью Pydanticмодели (перехватывая исключение ValidationError), и если возникает ошибка при чтении JWTтокена или при валидации данных с Pydantic, мы вызываем HTTPException, созданное ранее.

Для этого мы обновляем Pydanticмодель TokenData, добавляя новое свойство scopes.

Валидируя данные с помощью Pydantic, мы можем удостовериться, что у нас, например, именно list из str со scopes и str с username.

А не, скажем, dict или что‑то ещё — ведь это могло бы где‑то позже сломать приложение и создать риск для безопасности.

Мы также проверяем, что существует пользователь с таким именем, и если нет — вызываем то же исключение, созданное ранее.

{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}

Проверка scopes

Теперь проверяем, что все требуемые scopes — этой зависимостью и всеми зависящими (включая операции пути) — присутствуют среди scopes, предоставленных в полученном токене, иначе вызываем HTTPException.

Для этого используем security_scopes.scopes, содержащий list со всеми этими scopes как str.

{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}

Дерево зависимостей и scopes

Ещё раз рассмотрим дерево зависимостей и scopes.

Так как у зависимости get_current_active_user есть подзависимость get_current_user, scope "me", объявленный в get_current_active_user, будет включён в список требуемых scopes в security_scopes.scopes, передаваемый в get_current_user.

Сама операция пути тоже объявляет scope — "items", поэтому он также будет в списке security_scopes.scopes, передаваемом в get_current_user.

Иерархия зависимостей и scopes выглядит так:

  • Операция пути read_own_items:
    • Запрашивает scopes ["items"] с зависимостью:
    • get_current_active_user:
      • Функция‑зависимость get_current_active_user:
        • Запрашивает scopes ["me"] с зависимостью:
        • get_current_user:
          • Функция‑зависимость get_current_user:
            • Собственных scopes не запрашивает.
            • Имеет зависимость, использующую oauth2_scheme.
            • Имеет параметр security_scopes типа SecurityScopes:
              • Этот параметр security_scopes имеет свойство scopes с list, содержащим все объявленные выше scopes, то есть:
                • security_scopes.scopes будет содержать ["me", "items"] для операции пути read_own_items.
                • security_scopes.scopes будет содержать ["me"] для операции пути read_users_me, потому что он объявлен в зависимости get_current_active_user.
                • security_scopes.scopes будет содержать [] (ничего) для операции пути read_system_status, потому что там не объявлялся Security со scopes, и его зависимость get_current_user тоже не объявляет scopes.

/// tip | Совет

Важный и «магический» момент здесь в том, что get_current_user будет иметь разный список scopes для проверки для каждой операции пути.

Всё это зависит от scopes, объявленных в каждой операции пути и в каждой зависимости в дереве зависимостей конкретной операции пути.

///

Больше деталей о SecurityScopes

Вы можете использовать SecurityScopes в любой точке и в нескольких местах — необязательно в «корневой» зависимости.

Он всегда будет содержать security scopes, объявленные в текущих зависимостях Security, и всеми зависящими — для этой конкретной операции пути и этого конкретного дерева зависимостей.

Поскольку SecurityScopes будет содержать все scopes, объявленные зависящими, вы можете использовать его, чтобы централизованно проверять наличие требуемых scopes в токене в одной функции‑зависимости, а затем объявлять разные требования по scopes в разных операциях пути.

Они будут проверяться независимо для каждой операции пути.

Проверим это

Откройте документацию API — вы сможете аутентифицироваться и указать, какие scopes вы хотите авторизовать.

Если вы не выберете ни один scope, вы будете «аутентифицированы», но при попытке доступа к /users/me/ или /users/me/items/ получите ошибку о недостаточных разрешениях. При этом доступ к /status/ будет возможен.

Если вы выберете scope me, но не items, вы сможете получить доступ к /users/me/, но не к /users/me/items/.

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

О сторонних интеграциях

В этом примере мы используем OAuth2 «password flow» (аутентификация по паролю).

Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом.

Мы можем ему доверять при получении username и password, потому что он под нашим контролем.

Но если вы создаёте OAuth2приложение, к которому будут подключаться другие (т.е. вы строите провайдера аутентификации наподобие Facebook, Google, GitHub и т.п.), вам следует использовать один из других «flows».

Самый распространённый — «implicit flow».

Самый безопасный — «code flow», но он сложнее в реализации, так как требует больше шагов. Из‑за сложности многие провайдеры в итоге рекомендуют «implicit flow».

/// note | Примечание

Часто каждый провайдер аутентификации называет свои «flows» по‑разному — как часть бренда.

Но в итоге они реализуют один и тот же стандарт OAuth2.

///

FastAPI включает утилиты для всех этих OAuth2flows в fastapi.security.oauth2.

Security в параметре dependencies декоратора

Точно так же, как вы можете определить list из Depends в параметре dependencies декоратора (см. Зависимости в декораторах операции пути), вы можете использовать там и Security со scopes.