From a54e336b22b2ec15c85e977dfd149f805f281c54 Mon Sep 17 00:00:00 2001 From: Aleksandr Andrukhov Date: Tue, 10 Dec 2024 13:53:40 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=90=20Add=20Russian=20translation=20fo?= =?UTF-8?q?r=20`docs/ru/docs/tutorial/security/simple-oauth2.md`=20(#10599?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/tutorial/security/simple-oauth2.md | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 docs/ru/docs/tutorial/security/simple-oauth2.md diff --git a/docs/ru/docs/tutorial/security/simple-oauth2.md b/docs/ru/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..9732265cc --- /dev/null +++ b/docs/ru/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,272 @@ +# Простая авторизация по протоколу OAuth2 с токеном типа Bearer + +Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить безопасную систему. + +## Получение `имени пользователя` и `пароля` + +Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**. + +Протокол OAuth2 определяет, что при использовании "аутентификации по паролю" (которую мы и используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы. + +В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут. + +Но не волнуйтесь, вы можете показать его конечным пользователям во фронтенде в том виде, в котором хотите. + +А ваши модели баз данных могут использовать любые другие имена. + +Но при авторизации согласно спецификации, требуется использовать именно эти имена, что даст нам возможность воспользоваться встроенной системой документации API. + +В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет). + +### Oбласть видимости (scope) + +В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`". + +Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных областей видимости (scopes), разделенных пробелами. + +Каждая "область видимости" (scope) - это просто строка (без пробелов). + +Обычно они используются для указания уровней доступа, например: + +* `users:read` или `users:write` являются распространенными примерами. +* `instagram_basic` используется Facebook / Instagram. +* `https://www.googleapis.com/auth/drive` используется компанией Google. + +/// info | Дополнительнаяя информация +В OAuth2 "scope" - это просто строка, которая уточняет уровень доступа. + +Не имеет значения, содержит ли он другие символы, например `:`, или является ли он URL. + +Эти детали зависят от конкретной реализации. + +Для OAuth2 это просто строки. +/// + +## Код получения `имени пользователя` и `пароля` + +Для решения задачи давайте воспользуемся утилитами, предоставляемыми **FastAPI**. + +### `OAuth2PasswordRequestForm` + +Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте ее как зависимость с `Depends` в *эндпоинте* `/token`: + + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm` - это класс для использования в качестве зависимости для *функции обрабатывающей эндпоинт*, который определяет тело формы со следующими полями: + +* `username`. +* `password`. +* Необязательное поле `scope` в виде большой строки, состоящей из строк, разделенных пробелами. +* Необязательное поле `grant_type`. + +/// tip | Подсказка +По спецификации OAuth2 поле `grant_type` является обязательным и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого. + +Если вам необходимо использовать `grant_type`, воспользуйтесь `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`. +/// + +* Необязательное поле `client_id` (в нашем примере он не нужен). +* Необязательное поле `client_secret` (в нашем примере он не нужен). + +/// info | Дополнительная информация +Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как `OAuth2PasswordBearer`. + +`OAuth2PasswordBearer` указывает **FastAPI**, что это схема безопасности. Следовательно, она будет добавлена в OpenAPI. + +Но `OAuth2PasswordRequestForm` - это всего лишь класс зависимости, который вы могли бы написать самостоятельно или вы могли бы объявить параметры `Form` напрямую. + +Но, поскольку это распространённый вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу. +/// + +### Использование данных формы + +/// tip | Подсказка +В экземпляре зависимого класса `OAuth2PasswordRequestForm` атрибут `scope`, состоящий из одной длинной строки, разделенной пробелами, заменен на атрибут `scopes`, состоящий из списка отдельных строк, каждая из которых соответствует определенному уровню доступа. + +В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется. +/// + +Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы. + +Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль". + +Для ошибки мы используем исключение `HTTPException`: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### Проверка пароля + +На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль. + +Давайте сначала поместим эти данные в модель Pydantic `UserInDB`. + +Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей. + +Если пароли не совпадают, мы возвращаем ту же ошибку. + +#### Хеширование паролей + +"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина. + +Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину. + +Но преобразовать тарабарщину обратно в пароль невозможно. + +##### Зачем использовать хеширование паролей + +Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши. + +Таким образом, вор не сможет использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно). + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### Про `**user_dict` + +`UserInDB(**user_dict)` означает: + +*Передавать ключи и значения `user_dict` непосредственно в качестве аргументов ключ-значение, что эквивалентно:* + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +/// info | Дополнительная информация +Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user_indict){.internal-link target=_blank}. +/// + +## Возврат токена + +Ответ эндпоинта `token` должен представлять собой объект в формате JSON. + +Он должен иметь `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть "`bearer`". + +И в нем должна быть строка `access_token`, содержащая наш токен доступа. + +В этом простом примере мы нарушим все правила безопасности, и будем считать, что имя пользователя (username) полностью соответствует токену (token) + +/// tip | Подсказка +В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами JWT. + +Но пока давайте остановимся на необходимых нам деталях. +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip | Подсказка +Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере. + +Это то, что вы должны сделать сами в своем коде и убедиться, что вы используете эти JSON-ключи. + +Это практически единственное, что нужно не забывать делать самостоятельно, чтобы следовать требованиям спецификации. + +Все остальное за вас сделает **FastAPI**. +/// + +## Обновление зависимостей + +Теперь мы обновим наши зависимости. + +Мы хотим получить значение `current_user` *только* если этот пользователь активен. + +Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`. + +Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен. + +Таким образом, в нашем эндпоинте мы получим пользователя только в том случае, если он существует, правильно аутентифицирован и активен: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info | Дополнительная информация +Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации. + +Ответ сервера с HTTP-кодом 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`. + +В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`. + +На самом деле этот дополнительный заголовок можно пропустить и все будет работать. + +Но он приведён здесь для соответствия спецификации. + +Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей сейчас или в будущем. + +В этом и заключается преимущество стандартов... +/// + +## Посмотим как это работает + +Откроем интерактивную документацию: http://127.0.0.1:8000/docs. + +### Аутентификация + +Нажмите кнопку "Авторизация". + +Используйте учётные данные: + +Пользователь: `johndoe` + +Пароль: `secret` + + + +После авторизации в системе вы увидите следующее: + + + +### Получение собственных пользовательских данных + +Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, то будет выдана ошибка HTTP 401: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### Неактивный пользователь + +Теперь попробуйте пройти аутентификацию с неактивным пользователем: + +Пользователь: `alice` + +Пароль: `secret2` + +И попробуйте использовать операцию `GET` с путем `/users/me`. + +Вы получите ошибку "Inactive user", как тут: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## Резюме + +Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API. + +Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных. + + Единственным недостатком нашей системы является то, что она всё ещё не защищена. + +В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены JWT.