committed by
GitHub
1 changed files with 261 additions and 0 deletions
@ -0,0 +1,261 @@ |
|||
# 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>. |
|||
|
|||
## Установка `PyJWT` |
|||
|
|||
Нам необходимо установить `pyjwt` для генерации и проверки JWT-токенов на языке Python. |
|||
|
|||
Убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активируйте его, а затем установите `pyjwt`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pyjwt |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// info | Дополнительная информация |
|||
Если вы планируете использовать алгоритмы цифровой подписи, такие как RSA или ECDSA, вам следует установить зависимость библиотеки криптографии `pyjwt[crypto]`. |
|||
|
|||
Подробнее об этом можно прочитать в <a href=«https://pyjwt.readthedocs.io/en/latest/installation.html» class=«external-link» target=«_blank»>документации по установке PyJWT</a>. |
|||
/// |
|||
|
|||
## Хеширование паролей |
|||
|
|||
"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина. |
|||
|
|||
Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину. |
|||
|
|||
Но преобразовать тарабарщину обратно в пароль невозможно. |
|||
|
|||
### Для чего нужно хеширование паролей |
|||
|
|||
Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши. |
|||
|
|||
Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно). |
|||
|
|||
## Установка `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. |
|||
|
|||
И при этом быть совместимым со всеми этими системами. |
|||
/// |
|||
|
|||
Создайте служебную функцию для хэширования пароля, поступающего от пользователя. |
|||
|
|||
А затем создайте другую - для проверки соответствия полученного пароля и хранимого хэша. |
|||
|
|||
И еще одну - для аутентификации и возврата пользователя. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} |
|||
|
|||
/// 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, которая будет использоваться для формирования ответа на запрос на получение токена. |
|||
|
|||
Создайте служебную функцию для генерации нового токена доступа. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} |
|||
|
|||
## Обновление зависимостей |
|||
|
|||
Обновите `get_current_user` для получения того же токена, что и раньше, но на этот раз с использованием JWT-токенов. |
|||
|
|||
Декодируйте полученный токен, проверьте его и верните текущего пользователя. |
|||
|
|||
Если токен недействителен, то сразу же верните HTTP-ошибку. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} |
|||
|
|||
## Обновление *операции пути* `/token` |
|||
|
|||
Создайте `timedelta` со временем истечения срока действия токена. |
|||
|
|||
Создайте реальный токен доступа JWT и верните его |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} |
|||
|
|||
### Технические подробности о JWT ключе `sub` |
|||
|
|||
В спецификации JWT говорится, что существует ключ `sub`, содержащий субъект токена. |
|||
|
|||
Его использование необязательно, но это именно то место, куда вы должны поместить идентификатор пользователя, и поэтому мы здесь его и используем. |
|||
|
|||
JWT может использоваться и для других целей, помимо идентификации пользователя и предоставления ему возможности выполнять операции непосредственно в вашем API. |
|||
|
|||
Например, вы могли бы определить "автомобиль" или "запись в блоге". |
|||
|
|||
Затем вы могли бы добавить права доступа к этой сущности, например "управлять" (для автомобиля) или "редактировать" (для блога). |
|||
|
|||
Затем вы могли бы передать этот JWT-токен пользователю (или боту), и они использовали бы его для выполнения определенных действий (управление автомобилем или редактирование запись в блоге), даже не имея учетной записи, просто используя JWT-токен, сгенерированный вашим API. |
|||
|
|||
Используя эти идеи, JWT можно применять для гораздо более сложных сценариев. |
|||
|
|||
В отдельных случаях несколько сущностей могут иметь один и тот же идентификатор, скажем, `foo` (пользователь `foo`, автомобиль `foo` и запись в блоге `foo`). |
|||
|
|||
Поэтому, чтобы избежать коллизий идентификаторов, при создании JWT-токена для пользователя можно добавить префикс `username` к значению ключа `sub`. Таким образом, в данном примере значение `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"> |
|||
|
|||
Пройдите авторизацию так же, как делали раньше. |
|||
|
|||
Используя учетные данные пользователя: |
|||
|
|||
Username: `johndoe` |
|||
Password: `secret` |
|||
|
|||
/// check | Заметка |
|||
Обратите внимание, что нигде в коде не используется открытый текст пароля "`secret`", мы используем только его хэшированную версию. |
|||
/// |
|||
|
|||
<img src="/img/tutorial/security/image08.png"> |
|||
|
|||
Вызвав эндпоинт `/users/me/`, вы получите ответ в виде: |
|||
|
|||
```JSON |
|||
{ |
|||
"username": "johndoe", |
|||
"email": "[email protected]", |
|||
"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 существует понятие "диапазоны" ("`scopes`"). |
|||
|
|||
С их помощью можно добавить определенный набор разрешений к JWT-токену. |
|||
|
|||
Затем вы можете передать этот токен непосредственно пользователю или третьей стороне для взаимодействия с вашим API с определенным набором ограничений. |
|||
|
|||
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Руководстве пользователя**. |
|||
|
|||
## Резюме |
|||
|
|||
С учетом того, что вы видели до сих пор, вы можете создать безопасное приложение **FastAPI**, используя такие стандарты, как OAuth2 и JWT. |
|||
|
|||
Практически в любом фреймворке работа с безопасностью довольно быстро превращается в сложную тему. |
|||
|
|||
Многие пакеты, сильно упрощающие эту задачу, вынуждены идти на многочисленные компромиссы с моделью данных, с базой данных и с доступным функционалом. Некоторые из этих пакетов, которые пытаются уж слишком все упростить, имеют даже "дыры" в системе безопасности. |
|||
|
|||
--- |
|||
|
|||
**FastAPI** не делает уступок ни одной базе данных, модели данных или инструментарию. |
|||
|
|||
Он предоставляет вам полную свободу действий, позволяя выбирать то, что лучше всего подходит для вашего проекта. |
|||
|
|||
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `passlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов. |
|||
|
|||
Напротив, он предоставляет инструменты, позволяющие максимально упростить этот процесс без ущерба для гибкости, надежности и безопасности. |
|||
|
|||
При этом вы можете использовать и реализовывать безопасные стандартные протоколы, такие как OAuth2, относительно простым способом. |
|||
|
|||
В **Руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами - это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей. |
Loading…
Reference in new issue