From 2b2d346633a2d5dbd1c9f48de611a58c951b5a48 Mon Sep 17 00:00:00 2001 From: Andrukhov Aleksandr Date: Mon, 6 Nov 2023 19:39:07 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=90=20Add=20Russian=20translation=20fo?= =?UTF-8?q?r=20`docs/ru/docs/tutorial/security/oauth2-jwt.md`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ru/docs/tutorial/security/oauth2-jwt.md | 392 +++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 docs/ru/docs/tutorial/security/oauth2-jwt.md diff --git a/docs/ru/docs/tutorial/security/oauth2-jwt.md b/docs/ru/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 000000000..e1bef87ee --- /dev/null +++ b/docs/ru/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,392 @@ +# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами + +Теперь, когда у нас есть весь поток безопасности, давайте сделаем приложение действительно безопасным, используя токены JWT и безопасное хеширование паролей. + +Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д. + +Мы начнем с того места, на котором остановились в предыдущей главе, и начнем его расширять. + +## Про JWT + +JWT означает "JSON Web Tokens". + +Это стандарт для кодирования JSON-объекта в виде длинной строки без пробелов. Выглядит это следующим образом: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +Он не зашифрован, поэтому любой человек может восстановить информацию из его содержимого. + +Но он подписан. Таким образом, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что вы действительно его эмитировали. + +Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с токеном, вы будете знать, что он все еще авторизирован в вашей системе. + +Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать. + +Если вы хотите поиграть с JWT-токенами и посмотреть, как они работают, посмотрите https://jwt.io. +## Установка `python-jose` + +Нам необходимо установить `python-jose` для генерации и проверки JWT-токенов на языке Python: + +
+ +```console +$ pip install "python-jose[cryptography]" + +---> 100% +``` + +
+ +Python-jose требует криптографического бэкенда в качестве дополнения. + +Здесь мы используем рекомендуемый вариант: pyca/cryptography. + +!!! tip "Подсказка" + Ранее в этом руководстве использовался PyJWT. + + Но теперь он обновлен и вместо него используется Python-jose, поскольку он предоставляет все возможности PyJWT, а также некоторые дополнительные возможности, которые могут понадобиться в дальнейшем при создании интеграций с другими инструментами. + +## Хеширование паролей + +"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина. + +Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину. + +Но преобразовать тарабарщину обратно в пароль невозможно. + +### Для чего нужно хеширование паролей + +Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши. + +Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно). + +## Установка `passlib` + +PassLib - это отличный пакет Python для работы с хэшами паролей. + +Он поддерживает множество безопасных алгоритмов хеширования и утилит для работы с ними. + +Рекомендуемый алгоритм - "Bcrypt". + +Итак, установите PassLib вместе с Bcrypt: + +
+ +```console +$ pip install "passlib[bcrypt]" + +---> 100% +``` + +
+ +!!! 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-токенов. + +Для генерации безопасного случайного секретного ключа используйте команду: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +И скопируйте полученный результат в переменную `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` должен иметь уникальный идентификатор для всего приложения и представлять собой строку. + +## Проверьте его + +Запустите сервер и перейдите к документации: http://127.0.0.1:8000/docs. + +Вы увидите пользовательский интерфейс вида: + + + +Авторизуйте приложение так же, как и раньше. + +Using the credentials: + +Username: `johndoe` +Password: `secret` + +!!! check "Заметка" + Обратите внимание, что нигде в коде нет открытого текста пароля "`secret`", мы имеем только его хэшированную версию. + + + +Вызвав конечную точку `/users/me/`, вы получите ответ в виде: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +Если открыть инструменты разработчика, то можно увидеть, что передаваемые данные включают только токен, пароль передается только в первом запросе для аутентификации пользователя и получения токена доступа, но не в последующих: + + + +!!! 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 от имени их пользователей.