mirror of https://github.com/tiangolo/fastapi.git
🌐 Add Russian translation for `docs/ru/docs/tutorial/security/oauth2-jwt.md`
This commit is contained in:
parent
480620372a
commit
2b2d346633
|
|
@ -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 от имени их пользователей.
|
||||
Loading…
Reference in New Issue