Безопасность - первые шаги¶
Представим, что у вас есть свой бэкенд API на некотором домене.
И у вас есть фронтенд на другом домене или на другом пути того же домена (или в мобильном приложении).
И вы хотите иметь возможность аутентификации фронтенда с бэкендом, используя имя пользователя и пароль.
Мы можем использовать OAuth2 для создания такой системы с помощью FastAPI.
Но давайте избавим вас от необходимости читать всю длинную спецификацию, чтобы найти те небольшие кусочки информации, которые вам нужны.
Для работы с безопасностью воспользуемся средствами, предоставленными FastAPI.
Как это выглядит¶
Давайте сначала просто воспользуемся кодом и посмотрим, как он работает, а затем детально разберём, что происходит.
Создание main.py
¶
Скопируйте пример в файл main.py
:
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
Подсказка
Предпочтительнее использовать версию с аннотацией, если это возможно.
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
Запуск¶
Дополнительная информация
Вначале, установите библиотеку python-multipart
.
А именно: pip install python-multipart
.
Это связано с тем, что OAuth2 использует "данные формы" для передачи имени пользователя
и пароля
.
Запустите ваш сервер:
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Проверка¶
Перейдите к интерактивной документации по адресу: http://127.0.0.1:8000/docs.
Вы увидите примерно следующее:
Кнопка авторизации!
У вас уже появилась новая кнопка "Authorize".
А у операции пути теперь появился маленький замочек в правом верхнем углу, на который можно нажать.
При нажатии на нее появляется небольшая форма авторизации, в которую нужно ввести имя пользователя
и пароль
(и другие необязательные поля):
Технические детали
Неважно, что вы введете в форму, она пока не будет работать. Но мы к этому еще придем.
Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всех ваших API.
Он может использоваться командой фронтенда (которой можете быть и вы сами).
Он может быть использован сторонними приложениями и системами.
Кроме того, его можно использовать самостоятельно для отладки, проверки и тестирования одного и того же приложения.
Аутентификация по паролю¶
Теперь давайте вернемся немного назад и разберемся, что же это такое.
Аутентификация по паролю является одним из способов, определенных в OAuth2, для обеспечения безопасности и аутентификации.
OAuth2 был разработан для того, чтобы бэкэнд или API были независимы от сервера, который аутентифицирует пользователя.
Но в нашем случае одно и то же приложение FastAPI будет работать с API и аутентификацией.
Итак, рассмотрим его с этой упрощенной точки зрения:
- Пользователь вводит на фронтенде
имя пользователя
ипароль
и нажимаетEnter
. - Фронтенд (работающий в браузере пользователя) отправляет эти
имя пользователя
ипароль
на определенный URL в нашем API (объявленный с помощью параметраtokenUrl="token"
). - API проверяет эти
имя пользователя
ипароль
и выдает в ответ "токен" (мы еще не реализовали ничего из этого).- "Токен" - это просто строка с некоторым содержимым, которое мы можем использовать позже для верификации пользователя.
- Обычно срок действия токена истекает через некоторое время.
- Таким образом, пользователю придется снова войти в систему в какой-то момент времени.
- И если токен будет украден, то риск будет меньше, так как он не похож на постоянный ключ, который будет работать вечно (в большинстве случаев).
- Фронтенд временно хранит этот токен в каком-то месте.
- Пользователь щелкает мышью на фронтенде, чтобы перейти в другой раздел на фронтенде.
- Фронтенду необходимо получить дополнительные данные из API.
- Но для этого необходима аутентификация для конкретной конечной точки.
- Поэтому для аутентификации в нашем API он посылает заголовок
Authorization
со значениемBearer
плюс сам токен. - Если токен содержит
foobar
, то содержание заголовкаAuthorization
будет таким:Bearer foobar
.
Класс OAuth2PasswordBearer
в FastAPI¶
FastAPI предоставляет несколько средств на разных уровнях абстракции для реализации этих функций безопасности.
В данном примере мы будем использовать OAuth2, с аутентификацией по паролю, используя токен Bearer. Для этого мы используем класс OAuth2PasswordBearer
.
Дополнительная информация
Токен "bearer" - не единственный вариант, но для нашего случая он является наилучшим.
И это может быть лучшим вариантом для большинства случаев использования, если только вы не являетесь экспертом в области OAuth2 и точно знаете, почему вам лучше подходит какой-то другой вариант.
В этом случае FastAPI также предоставляет инструменты для его реализации.
При создании экземпляра класса OAuth2PasswordBearer
мы передаем в него параметр tokenUrl
. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки имени пользователя
и пароля
с целью получения токена.
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
Подсказка
Предпочтительнее использовать версию с аннотацией, если это возможно.
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
Подсказка
Здесь tokenUrl="token"
ссылается на относительный URL token
, который мы еще не создали. Поскольку это относительный URL, он эквивалентен ./token
.
Поскольку мы используем относительный URL, если ваш API расположен по адресу https://example.com/
, то он будет ссылаться на https://example.com/token
. Если же ваш API расположен по адресу https://example.com/api/v1/
, то он будет ссылаться на https://example.com/api/v1/token
.
Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таких сложных случаях, как оно находится за прокси-сервером.
Этот параметр не создает конечную точку / операцию пути, а объявляет, что URL /token
будет таким, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации API.
Вскоре мы создадим и саму операцию пути.
Дополнительная информация
Если вы очень строгий "питонист", то вам может не понравиться стиль названия параметра tokenUrl
вместо token_url
.
Это связано с тем, что тут используется то же имя, что и в спецификации OpenAPI. Таким образом, если вам необходимо более подробно изучить какую-либо из этих схем безопасности, вы можете просто использовать копирование/вставку, чтобы найти дополнительную информацию о ней.
Переменная oauth2_scheme
является экземпляром OAuth2PasswordBearer
, но она также является "вызываемой".
Ее можно вызвать следующим образом:
oauth2_scheme(some, parameters)
Поэтому ее можно использовать вместе с Depends
.
Использование¶
Теперь вы можете передать ваш oauth2_scheme
в зависимость с помощью Depends
.
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
Подсказка
Предпочтительнее использовать версию с аннотацией, если это возможно.
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
Эта зависимость будет предоставлять строку
, которая присваивается параметру token
в функции операции пути.
FastAPI будет знать, что он может использовать эту зависимость для определения "схемы безопасности" в схеме OpenAPI (и автоматической документации по API).
Технические детали
FastAPI будет знать, что он может использовать класс OAuth2PasswordBearer
(объявленный в зависимости) для определения схемы безопасности в OpenAPI, поскольку он наследуется от fastapi.security.oauth2.OAuth2
, который, в свою очередь, наследуется от fastapi.security.base.SecurityBase
.
Все утилиты безопасности, интегрируемые в OpenAPI (и автоматическая документация по API), наследуются от SecurityBase
, поэтому FastAPI может знать, как интегрировать их в OpenAPI.
Что он делает¶
Он будет искать в запросе заголовок Authorization
и проверять, содержит ли он значение Bearer
с некоторым токеном, и возвращать токен в виде строки
.
Если он не видит заголовка Authorization
или значение не имеет токена Bearer
, то в ответ будет выдана ошибка с кодом состояния 401 (UNAUTHORIZED
).
Для возврата ошибки даже не нужно проверять, существует ли токен. Вы можете быть уверены, что если ваша функция будет выполнилась, то в этом токене есть строка
.
Проверить это можно уже сейчас в интерактивной документации:
Мы пока не проверяем валидность токена, но для начала неплохо.
Резюме¶
Таким образом, всего за 3-4 дополнительные строки вы получаете некую примитивную форму защиты.