mirror of https://github.com/tiangolo/fastapi.git
🌐 Add Russian translation for `docs/ru/docs/tutorial/security/simple-oauth2.md`
This commit is contained in:
parent
480620372a
commit
a33d968767
|
|
@ -0,0 +1,433 @@
|
|||
# Простой OAuth2 с паролем и Bearer
|
||||
|
||||
Теперь давайте, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить полную безопасности.
|
||||
|
||||
## Получение `имени пользователя` и `пароля`
|
||||
|
||||
Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**.
|
||||
|
||||
OAuth2 определяет, что при использовании "аутентификации по паролю" (которую мы и используем) клиент/пользователь должен передавать поля `username` и `password` в качестве данных формы.
|
||||
|
||||
В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.
|
||||
|
||||
Но не волнуйтесь, вы можете показать его конечным пользователям во фронтенде в том виде, в котором хотите.
|
||||
|
||||
А ваши модели баз данных могут использовать любые другие имена.
|
||||
|
||||
Но в *операции пути* нам необходимо использовать именно эти имена, чтобы соответствовать спецификации (и иметь возможность, например, использовать это во встроенной системе документации API).
|
||||
|
||||
В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).
|
||||
|
||||
### `Scope`
|
||||
|
||||
В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`".
|
||||
|
||||
Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка "scopes", разделенными пробелами.
|
||||
|
||||
Каждая "область видимости" - это просто строка (без пробелов).
|
||||
|
||||
Обычно они используются, например, для объявления определенных разрешений безопасности:
|
||||
|
||||
* `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`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="4 78"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="4 78"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+"
|
||||
|
||||
```Python hl_lines="4 79"
|
||||
{!> ../../../docs_src/security/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="2 74"
|
||||
{!> ../../../docs_src/security/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="4 76"
|
||||
{!> ../../../docs_src/security/tutorial003.py!}
|
||||
```
|
||||
|
||||
`OAuth2PasswordRequestForm` - это зависимость от класса, которая объявляет тело формы со следующими полями:
|
||||
|
||||
* `username`.
|
||||
* `password`.
|
||||
* Необязательное поле `scope` в виде большой строки, состоящей из строк, разделенных пробелами.
|
||||
* Необязательное поле `grant_type`.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
В спецификации OAuth2 действительно *требуется* поле `grant_type` с фиксированным значением `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого.
|
||||
|
||||
Если вам необходимо обеспечить это, используйте `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
|
||||
|
||||
* Необязательное поле `client_id` (в нашем примере он не нужен).
|
||||
* Необязательное поле `client_secret` (в нашем примере он не нужен).
|
||||
|
||||
!!! info "Дополнительная информация"
|
||||
Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как и `OAuth2PasswordBearer`.
|
||||
|
||||
`OAuth2PasswordBearer` дает понять **FastAPI**, что это схема безопасности. Поэтому она добавляется в OpenAPI именно таким образом.
|
||||
|
||||
Но `OAuth2PasswordRequestForm` - это всего лишь зависимость от класса, которую вы могли бы написать сами, или объявить параметры `Form` напрямую.
|
||||
|
||||
Но поскольку это распространенный вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу.
|
||||
|
||||
### Использование данных формы
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Экземпляр зависимого класса `OAuth2PasswordRequestForm` не будет иметь атрибута `scope` с длинной строкой, разделенной пробелами, вместо этого он будет иметь атрибут `scopes` с фактическим списком строк для каждого отправляемого диапазона.
|
||||
|
||||
В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется.
|
||||
|
||||
Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `имя пользователя` из поля формы.
|
||||
|
||||
Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль".
|
||||
|
||||
Для ошибки мы используем исключение `HTTPException`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="3 79-81"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="3 79-81"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+"
|
||||
|
||||
```Python hl_lines="3 80-82"
|
||||
{!> ../../../docs_src/security/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="1 75-77"
|
||||
{!> ../../../docs_src/security/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="3 77-79"
|
||||
{!> ../../../docs_src/security/tutorial003.py!}
|
||||
```
|
||||
|
||||
### Проверка пароля
|
||||
|
||||
На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.
|
||||
|
||||
Давайте сначала поместим эти данные в модель Pydantic `UserInDB`.
|
||||
|
||||
Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать систему хеширования паролей.
|
||||
|
||||
Если пароли не совпадают, мы возвращаем ту же ошибку.
|
||||
|
||||
#### Хеширование паролей
|
||||
|
||||
"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
|
||||
|
||||
Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину.
|
||||
|
||||
Но преобразовать тарабарщину обратно в пароль невозможно.
|
||||
|
||||
##### Зачем использовать хеширование паролей
|
||||
|
||||
Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши.
|
||||
|
||||
Таким образом, вор не сможет попытаться использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно).
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="82-85"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="82-85"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+"
|
||||
|
||||
```Python hl_lines="83-86"
|
||||
{!> ../../../docs_src/security/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="78-81"
|
||||
{!> ../../../docs_src/security/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="80-83"
|
||||
{!> ../../../docs_src/security/tutorial003.py!}
|
||||
```
|
||||
|
||||
#### Про `**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`, содержащая наш токен доступа.
|
||||
|
||||
Для этого простого примера мы будем совершенно небезопасны и вернем то же самое `имя пользователя`, что и токен.
|
||||
|
||||
!!! tip "Подсказка"
|
||||
В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens">JWT</abbr>.
|
||||
|
||||
Но пока давайте остановимся на необходимых нам деталях.
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="87"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="87"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+"
|
||||
|
||||
```Python hl_lines="88"
|
||||
{!> ../../../docs_src/security/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="83"
|
||||
{!> ../../../docs_src/security/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="85"
|
||||
{!> ../../../docs_src/security/tutorial003.py!}
|
||||
```
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.
|
||||
|
||||
Это то, что вы должны сделать сами в своем коде, и убедиться, что вы используете эти JSON-ключи.
|
||||
|
||||
Это практически единственное, что нужно не забывать делать самостоятельно, чтобы соответствовать спецификации.
|
||||
|
||||
В остальном за вас это сделает **FastAPI**.
|
||||
|
||||
## Обновление зависимостей
|
||||
|
||||
Теперь мы обновим наши зависимости.
|
||||
|
||||
Мы хотим получить значение `current_user` *только* если этот пользователь активен.
|
||||
|
||||
Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
|
||||
|
||||
Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.
|
||||
|
||||
Таким образом, в нашей конечной точке мы получим пользователя только в том случае, если он существует, правильно аутентифицирован и активен:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="58-66 69-74 94"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="58-66 69-74 94"
|
||||
{!> ../../../docs_src/security/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+"
|
||||
|
||||
```Python hl_lines="59-67 70-75 95"
|
||||
{!> ../../../docs_src/security/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="56-64 67-70 88"
|
||||
{!> ../../../docs_src/security/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.8+ без Annotated"
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Предпочтительнее использовать версию с аннотацией, если это возможно.
|
||||
|
||||
```Python hl_lines="58-66 69-72 90"
|
||||
{!> ../../../docs_src/security/tutorial003.py!}
|
||||
```
|
||||
|
||||
!!! info "Дополнительная информация"
|
||||
Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
|
||||
|
||||
Любой HTTP с кодом состояния 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
|
||||
|
||||
В случае с токенами-носителями (наш случай) значение этого заголовка должно быть `Bearer`.
|
||||
|
||||
На самом деле этот дополнительный заголовок можно пропустить, и все будет работать.
|
||||
|
||||
Но он приведен здесь для соответствия спецификации.
|
||||
|
||||
Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей, сейчас или в будущем.
|
||||
|
||||
В этом и заключается преимущество стандартов...
|
||||
|
||||
## Посмотим в действии
|
||||
|
||||
Откроем интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
### Аутентификация
|
||||
|
||||
Нажмите кнопку "Авторизация".
|
||||
|
||||
Используйте учетные данные:
|
||||
|
||||
Пользователь: `johndoe`
|
||||
|
||||
Пароль: `secret`
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
После авторизации в системе вы увидите следующее:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### Получение собственных пользовательских данных
|
||||
|
||||
Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false,
|
||||
"hashed_password": "fakehashedsecret"
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию еще раз, то будет выдана ошибка HTTP 401:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
### Неактивный пользователь
|
||||
|
||||
Теперь попробуйте с неактивным пользователем, пройдите аутентификацию:
|
||||
|
||||
Пользователь: `alice`
|
||||
|
||||
Пароль: `secret2`
|
||||
|
||||
И попробуйте использовать операцию `GET` с путем `/users/me`.
|
||||
|
||||
Вы получите ошибку "Inactive user", как тут:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Inactive user"
|
||||
}
|
||||
```
|
||||
|
||||
## Резюме
|
||||
|
||||
Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API.
|
||||
|
||||
Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных.
|
||||
|
||||
Единственная деталь, которой не хватает - это то, что она еще не является фактически "защищенной".
|
||||
|
||||
В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens">JWT</abbr>.
|
||||
Loading…
Reference in New Issue