🌐 Add Russian translation for `docs/ru/docs/tutorial/security/oauth2-jwt.md`

This commit is contained in:
Andrukhov Aleksandr 2023-11-06 19:39:07 +03:00
parent 480620372a
commit 2b2d346633
1 changed files with 392 additions and 0 deletions

View File

@ -0,0 +1,392 @@
# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами
Теперь, когда у нас есть весь поток безопасности, давайте сделаем приложение действительно безопасным, используя токены <abbr title="JSON Web Tokens">JWT</abbr> и безопасное хеширование паролей.
Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д.
Мы начнем с того места, на котором остановились в предыдущей главе, и начнем его расширять.
## Про JWT
JWT означает "JSON Web Tokens".
Это стандарт для кодирования JSON-объекта в виде длинной строки без пробелов. Выглядит это следующим образом:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
Он не зашифрован, поэтому любой человек может восстановить информацию из его содержимого.
Но он подписан. Таким образом, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что вы действительно его эмитировали.
Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с токеном, вы будете знать, что он все еще авторизирован в вашей системе.
Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать.
Если вы хотите поиграть с JWT-токенами и посмотреть, как они работают, посмотрите <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
## Установка `python-jose`
Нам необходимо установить `python-jose` для генерации и проверки JWT-токенов на языке Python:
<div class="termy">
```console
$ pip install "python-jose[cryptography]"
---> 100%
```
</div>
<a href="https://github.com/mpdavis/python-jose" class="external-link" target="_blank">Python-jose</a> требует криптографического бэкенда в качестве дополнения.
Здесь мы используем рекомендуемый вариант: <a href="https://cryptography.io/" class="external-link" target="_blank">pyca/cryptography</a>.
!!! tip "Подсказка"
Ранее в этом руководстве использовался <a href="https://pyjwt.readthedocs.io/" class="external-link" target="_blank">PyJWT</a>.
Но теперь он обновлен и вместо него используется Python-jose, поскольку он предоставляет все возможности PyJWT, а также некоторые дополнительные возможности, которые могут понадобиться в дальнейшем при создании интеграций с другими инструментами.
## Хеширование паролей
"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину.
Но преобразовать тарабарщину обратно в пароль невозможно.
### Для чего нужно хеширование паролей
Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши.
Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно).
## Установка `passlib`
PassLib - это отличный пакет Python для работы с хэшами паролей.
Он поддерживает множество безопасных алгоритмов хеширования и утилит для работы с ними.
Рекомендуемый алгоритм - "Bcrypt".
Итак, установите PassLib вместе с Bcrypt:
<div class="termy">
```console
$ pip install "passlib[bcrypt]"
---> 100%
```
</div>
!!! tip "Подсказка"
С помощью `passlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками.
Таким образом, вы сможете, например, совместно использовать одни и те же данные из приложения Django в базе данных с приложением FastAPI. Или постепенно мигрировать Django-приложение, используя ту же базу данных.
При этом пользователи смогут одновременно входить в систему как из приложения Django, так и из приложения **FastAPI**.
## Хеширование и проверка паролей
Импортируйте необходимые инструменты из `passlib`.
Создайте "контекст" PassLib. Именно он будет использоваться для хэширования и проверки паролей.
!!! tip "Подсказка"
Контекст PassLib также имеет функциональность для использования различных алгоритмов хеширования, в том числе и устаревших, только для возможности их проверки и т.д.
Например, вы можете использовать его для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Bcrypt.
И при этом быть совместимым со всеми этими системами.
Создайте служебную функцию для хэширования пароля, поступающего от пользователя.
Еще одну для проверки соответствия полученного пароля и хранимого хэша.
И еще один - для аутентификации и возврата пользователя.
=== "Python 3.10+"
```Python hl_lines="7 48 55-56 59-60 69-75"
{!> ../../../docs_src/security/tutorial004_an_py310.py!}
```
=== "Python 3.9+"
```Python hl_lines="7 48 55-56 59-60 69-75"
{!> ../../../docs_src/security/tutorial004_an_py39.py!}
```
=== "Python 3.8+"
```Python hl_lines="7 49 56-57 60-61 70-76"
{!> ../../../docs_src/security/tutorial004_an.py!}
```
=== "Python 3.10+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="6 47 54-55 58-59 68-74"
{!> ../../../docs_src/security/tutorial004_py310.py!}
```
=== "Python 3.8+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="7 48 55-56 59-60 69-75"
{!> ../../../docs_src/security/tutorial004.py!}
```
!!! note "Технические детали"
Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
## Работа с JWT токенами
Импортируйте установленные модули.
Создайте случайный секретный ключ, который будет использоваться для подписи JWT-токенов.
Для генерации безопасного случайного секретного ключа используйте команду:
<div class="termy">
```console
$ openssl rand -hex 32
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
```
</div>
И скопируйте полученный результат в переменную `SECRET_KEY` (не используйте тот, что в примере).
Создайте переменную `ALGORITHM` с алгоритмом, используемым для подписи JWT-токена, и установите для нее значение `"HS256"`.
Создайте переменную для срока действия токена.
Определите Pydantic Model, которая будет использоваться в конечной точке для ответа на токен.
Создайте служебную функцию для генерации нового токена доступа.
=== "Python 3.10+"
```Python hl_lines="6 12-14 28-30 78-86"
{!> ../../../docs_src/security/tutorial004_an_py310.py!}
```
=== "Python 3.9+"
```Python hl_lines="6 12-14 28-30 78-86"
{!> ../../../docs_src/security/tutorial004_an_py39.py!}
```
=== "Python 3.8+"
```Python hl_lines="6 13-15 29-31 79-87"
{!> ../../../docs_src/security/tutorial004_an.py!}
```
=== "Python 3.10+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="5 11-13 27-29 77-85"
{!> ../../../docs_src/security/tutorial004_py310.py!}
```
=== "Python 3.8+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="6 12-14 28-30 78-86"
{!> ../../../docs_src/security/tutorial004.py!}
```
## Обновление зависимостей
Обновите `get_current_user` для получения того же токена, что и раньше, но на этот раз с использованием JWT-токенов.
Декодирует полученный токен, проверяет его и возвращает текущего пользователя.
Если токен недействителен, то сразу же возвращается HTTP-ошибка.
=== "Python 3.10+"
```Python hl_lines="89-106"
{!> ../../../docs_src/security/tutorial004_an_py310.py!}
```
=== "Python 3.9+"
```Python hl_lines="89-106"
{!> ../../../docs_src/security/tutorial004_an_py39.py!}
```
=== "Python 3.8+"
```Python hl_lines="90-107"
{!> ../../../docs_src/security/tutorial004_an.py!}
```
=== "Python 3.10+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="88-105"
{!> ../../../docs_src/security/tutorial004_py310.py!}
```
=== "Python 3.8+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="89-106"
{!> ../../../docs_src/security/tutorial004.py!}
```
## Обновление *операции пути* `/token`
Создайте `timedelta` со временем истечения срока действия токена.
Создайте реальный токен доступа JWT и верните его
=== "Python 3.10+"
```Python hl_lines="117-132"
{!> ../../../docs_src/security/tutorial004_an_py310.py!}
```
=== "Python 3.9+"
```Python hl_lines="117-132"
{!> ../../../docs_src/security/tutorial004_an_py39.py!}
```
=== "Python 3.8+"
```Python hl_lines="118-133"
{!> ../../../docs_src/security/tutorial004_an.py!}
```
=== "Python 3.10+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="114-127"
{!> ../../../docs_src/security/tutorial004_py310.py!}
```
=== "Python 3.8+ без Annotated"
!!! tip "Подсказка"
Предпочтительнее использовать версию с аннотацией, если это возможно.
```Python hl_lines="115-128"
{!> ../../../docs_src/security/tutorial004.py!}
```
### Технические подробности о JWT ключе `sub`
В спецификации JWT говорится, что существует ключ `sub`, содержащий субъект токена.
Его использование необязательно, но это место, куда вы поместите идентификатор пользователя, поэтому мы здесь его используем.
JWT может использоваться и для других целей, помимо идентификации пользователя и предоставления ему возможности выполнять операции непосредственно в вашем API.
Например, можно определить "автомобиль" или "запись в блоге".
Затем можно добавить права доступа к этой сущности, например "управлять" (для автомобиля) или "редактировать" (для блога).
Затем вы можете передать этот JWT-токен пользователю (или боту), и он сможет использовать его для выполнения этих действий (управлять автомобилем или редактировать запись в блоге), даже не имея учетной записи, просто используя JWT-токен, сгенерированный вашим API для этого.
Используя эти идеи, JWT можно применять для гораздо более сложных сценариев.
В этих случаях несколько таких сущностей могут иметь один и тот же идентификатор, скажем, `foo` (пользователь `foo`, автомобиль `foo` и запись в блоге `foo`).
Поэтому, чтобы избежать коллизий идентификаторов, при создании JWT-токена для пользователя можно добавить префикс для значение ключа `sub`, например, с `username:`. Так, в данном примере значение `sub` могло бы быть таким: `username:johndoe`.
Важно помнить, что ключ `sub` должен иметь уникальный идентификатор для всего приложения и представлять собой строку.
## Проверьте его
Запустите сервер и перейдите к документации: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите пользовательский интерфейс вида:
<img src="/img/tutorial/security/image07.png">
Авторизуйте приложение так же, как и раньше.
Using the credentials:
Username: `johndoe`
Password: `secret`
!!! check "Заметка"
Обратите внимание, что нигде в коде нет открытого текста пароля "`secret`", мы имеем только его хэшированную версию.
<img src="/img/tutorial/security/image08.png">
Вызвав конечную точку `/users/me/`, вы получите ответ в виде:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false
}
```
<img src="/img/tutorial/security/image09.png">
Если открыть инструменты разработчика, то можно увидеть, что передаваемые данные включают только токен, пароль передается только в первом запросе для аутентификации пользователя и получения токена доступа, но не в последующих:
<img src="/img/tutorial/security/image10.png">
!!! note "Техническая информация"
Обратите внимание на заголовок `Authorization`, значение которого начинается с `Bearer`.
## Расширенное использование `scopes`
В OAuth2 существует понятие "диапазоны".
С их помощью можно добавить определенный набор разрешений к JWT-токену.
Затем вы можете передать этот токен непосредственно пользователю или третьей стороне для взаимодействия с вашим API с определенным набором ограничений.
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Руководстве пользователя**.
## Резюме
С учетом того, что вы видели до сих пор, вы можете создать безопасное приложение **FastAPI**, используя такие стандарты, как OAuth2 и JWT.
Практически в любом фреймворке работа с безопасностью довольно быстро превращается в сложную тему.
Многие пакеты, сильно упрощающие эту задачу, вынуждены идти на многочисленные компромиссы с моделью данных, базой данных и доступными функциями. А некоторые из этих пакетов, которые слишком упрощают работу, на самом деле имеют недостатки в системе безопасности.
---
**FastAPI** не идет на компромисс ни с одной базой данных, моделью данных или инструментом.
Он предоставляет вам полную свободу действий, позволяя выбирать те из них, которые лучше всего подходят для вашего проекта.
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `passlib` и `python-jose`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
Но он предоставляет инструменты, позволяющие максимально упростить этот процесс без ущерба для гибкости, надежности и безопасности.
При этом вы можете использовать и реализовывать безопасные стандартные протоколы, такие как OAuth2, относительно простым способом.
В **Руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" OAuth2 для создания более тонкой системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами - это механизм, используемый многими крупными провайдерами аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и др. для авторизации сторонних приложений для взаимодействия с их API от имени их пользователей.