docs/ru/docs/tutorial/security/simple-oauth2.md MARKDOWN 274 lines View on github.com → Search inside
1# Простая авторизация OAuth2 с паролем и «Bearer» { #simple-oauth2-with-password-and-bearer }23Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить полный поток безопасности.45## Получение `username` и `password` { #get-the-username-and-password }67Для получения `username` и `password` мы будем использовать утилиты безопасности **FastAPI**.89OAuth2 определяет, что при использовании "password flow" (аутентификация по паролю - именно его мы используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.1011В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.1213Но не волнуйтесь, вы можете показать это конечным пользователям во фронтенде в том виде, в котором хотите.1415А ваши модели баз данных могут использовать любые другие имена.1617Но для *операции пути* входа в систему нам нужно использовать именно эти имена, чтобы быть совместимыми со спецификацией (и иметь возможность, например, использовать встроенную систему документации API).1819В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).2021### `scope` { #scope }2223В спецификации также говорится, что клиент может передать еще одно поле формы — `scope`.2425Имя поля формы — `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных "scopes", разделенных пробелами.2627Каждый "scope" — это просто строка (без пробелов).2829Обычно они используются для указания уровней доступа, например:3031* `users:read` или `users:write` — распространенные примеры.32* `instagram_basic` используется Facebook / Instagram.33* `https://www.googleapis.com/auth/drive` используется Google.3435/// note | Примечание36В OAuth2 "scope" — это просто строка, которая указывает требуемое конкретное разрешение.3738Не имеет значения, содержит ли она другие символы, например `:`, или является ли это URL.3940Эти детали зависят от реализации.4142Для OAuth2 это просто строки.43///4445## Код для получения `username` и `password` { #code-to-get-the-username-and-password }4647Теперь воспользуемся утилитами, предоставляемыми **FastAPI**, чтобы обработать это.4849### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }5051Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте её как зависимость с `Depends` в операции пути для `/token`:5253{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}5455`OAuth2PasswordRequestForm` — это зависимость-класс, которая объявляет тело формы со следующими полями:5657* `username`.58* `password`.59* Необязательное поле `scope` в виде большой строки, состоящей из строк, разделенных пробелами.60* Необязательное поле `grant_type`.6162/// tip | Подсказка63По спецификации OAuth2 поле `grant_type` обязательно и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` это не проверяет строго.6465Если вам нужно это строгое требование, используйте `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.66///6768* Необязательное поле `client_id` (в нашем примере оно не нужно).69* Необязательное поле `client_secret` (в нашем примере оно не нужно).7071/// note | Примечание72`OAuth2PasswordRequestForm` — это не специальный класс для **FastAPI**, как `OAuth2PasswordBearer`.7374`OAuth2PasswordBearer` сообщает **FastAPI**, что это схема безопасности. Поэтому она добавляется в OpenAPI соответствующим образом.7576А `OAuth2PasswordRequestForm` — это просто зависимость-класс, которую вы могли бы написать сами, или вы могли бы объявить параметры `Form` напрямую.7778Но так как это распространённый вариант использования, он предоставлен **FastAPI** напрямую, чтобы упростить задачу.79///8081### Использование данных формы { #use-the-form-data }8283/// tip | Подсказка84У экземпляра зависимости `OAuth2PasswordRequestForm` не будет атрибута `scope` с длинной строкой, разделенной пробелами. Вместо этого будет атрибут `scopes` со списком отдельных строк — по одной для каждого переданного scope.8586В данном примере мы не используем `scopes`, но если вам это необходимо, функциональность есть.87///8889Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы.9091Если такого пользователя нет, то мы возвращаем ошибку "Incorrect username or password".9293Для ошибки используем исключение `HTTPException`:9495{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}9697### Проверка пароля { #check-the-password }9899На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.100101Давайте сначала поместим эти данные в Pydantic-модель `UserInDB`.102103Никогда нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.104105Если пароли не совпадают, мы возвращаем ту же ошибку.106107#### Хеширование паролей { #password-hashing }108109"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.110111Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину.112113Но преобразовать тарабарщину обратно в пароль невозможно.114115##### Зачем использовать хеширование паролей { #why-use-password-hashing }116117Если вашу базу данных украдут, у злоумышленника не будет паролей пользователей в открытом виде, только хэши.118119Таким образом, он не сможет попробовать использовать эти же пароли в другой системе (поскольку многие пользователи используют один и тот же пароль повсеместно, это было бы опасно).120121{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}122123#### Про `**user_dict` { #about-user-dict }124125`UserInDB(**user_dict)` означает:126127*Передать ключи и значения `user_dict` непосредственно как аргументы ключ-значение, эквивалентно:*128129```Python130UserInDB(131    username = user_dict["username"],132    email = user_dict["email"],133    full_name = user_dict["full_name"],134    disabled = user_dict["disabled"],135    hashed_password = user_dict["hashed_password"],136)137```138139/// note | Примечание140Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user-in-model-dump).141///142143## Возврат токена { #return-the-token }144145Ответ эндпоинта `token` должен быть объектом JSON.146147В нём должен быть `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть `bearer`.148149И в нём должен быть `access_token` — строка, содержащая наш токен доступа.150151В этом простом примере мы намеренно поступим небезопасно и вернём тот же `username` в качестве токена.152153/// tip | Подсказка154В следующей главе вы увидите реальную защищённую реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens - JSON веб-токены">JWT</abbr>.155156Но пока давайте сосредоточимся на необходимых нам деталях.157///158159{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}160161/// tip | Подсказка162Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.163164Это то, что вы должны сделать сами в своём коде и убедиться, что вы используете именно эти JSON-ключи.165166Это практически единственное, о чём нужно не забыть, чтобы соответствовать спецификациям.167168Остальное за вас сделает **FastAPI**.169///170171## Обновление зависимостей { #update-the-dependencies }172173Теперь мы обновим наши зависимости.174175Мы хотим получить `current_user` только если этот пользователь активен.176177Поэтому мы создаём дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.178179Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.180181Таким образом, в нашей операции пути мы получим пользователя только в том случае, если он существует, корректно аутентифицирован и активен:182183{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}184185/// note | Примечание186Дополнительный HTTP-заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.187188Любой HTTP статус-код 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.189190В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`.191192Фактически, этот дополнительный заголовок можно опустить, и всё будет работать.193194Но он приведён здесь для соответствия спецификациям.195196Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей — сейчас или в будущем.197198В этом и заключается преимущество стандартов...199///200201## Посмотрим, как это работает { #see-it-in-action }202203Откройте интерактивную документацию: [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).204205### Аутентификация { #authenticate }206207Нажмите кнопку "Authorize".208209Используйте учётные данные:210211Пользователь: `johndoe`212213Пароль: `secret`214215<img src="/img/tutorial/security/image04.png">216217После аутентификации вы увидите следующее:218219<img src="/img/tutorial/security/image05.png">220221### Получение собственных пользовательских данных { #get-your-own-user-data }222223Теперь используйте операцию `GET` с путём `/users/me`.224225Вы получите свои пользовательские данные, например:226227```JSON228{229  "username": "johndoe",230  "email": "johndoe@example.com",231  "full_name": "John Doe",232  "disabled": false,233  "hashed_password": "fakehashedsecret"234}235```236237<img src="/img/tutorial/security/image06.png">238239Если щёлкнуть на значке замка и выйти из системы, а затем попробовать выполнить ту же операцию ещё раз, будет выдана ошибка HTTP 401:240241```JSON242{243  "detail": "Not authenticated"244}245```246247### Неактивный пользователь { #inactive-user }248249Теперь попробуйте с неактивным пользователем, аутентифицируйтесь с:250251Пользователь: `alice`252253Пароль: `secret2`254255И попробуйте использовать операцию `GET` с путём `/users/me`.256257Вы получите ошибку "Inactive user", как здесь:258259```JSON260{261  "detail": "Inactive user"262}263```264265## Резюме { #recap }266267Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `username` и `password` для вашего API.268269Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных и с любой моделью пользователя или моделью данных.270271Единственная деталь, которой не хватает, — система пока ещё не "защищена" по-настоящему.272273В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens - JSON веб-токены">JWT</abbr>.

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings