Аутентификация
Аутентификация на веб-сайтах, как правило, устроена так: при входе на сайт вы один раз вводите логин и пароль, система проверяет ваши данные и, в соответствии с вашими правами, предоставляет вам доступ к сайту. При переходе по страницам этого сайта вам не приходится каждый раз заново вводить свои данные: система запоминает вас, «сохраняет ваше состояние».
Но для REST API такой вариант не подходит: отсутствие состояния (stateless) — это один из основных принципов REST. Этот принцип означает, что каждый запрос к серверу не должен быть связан с предыдущими запросами, как будто их и не было.
Если провести аналогию с веб-сайтами — REST предполагает, что при каждом переходе на новую страницу вам снова и снова придётся вводить логин и пароль.
Аутентификация на основе токенов
Аутентификация с помощью токенов отлично вписывается в архитектуру REST. Клиент отправляет токен, кодовую последовательность символов, в заголовке каждого запроса к серверу. Если в базе данных существует пользователь, которому выдавался этот токен, то запрос будет обработан. Если токен не соответствует ни одному пользователю — запрос будет отклонён.
При каждом последующем запросе к API клиент также должен передавать этот токен, и каждый раз будет проводиться проверка прав доступа.
Пишем свою аутентификацию
В актуальной версии Kittygram изменять данные может любой желающий. Это не лучший вариант.
Настроим проект так, чтобы отправлять запросы к сервису могли только аутентифицированные пользователи. Прикрутим к проекту механизм аутентификации по токену.
Порядок действий для пользователя будет таким:
- Пользователь создаёт на проекте свою учётную запись, для входа в систему у него будет логин и пароль.
- Клиент отправляет запрос на специальный эндпоинт и в запросе передаёт логин и пароль. Если в базе данных существует такой пользователь и пароль совпадает с сохранённым в базе — в ответ клиент получает токен.
- Теперь клиент может работать с API, но при каждом запросе он должен отправлять токен.
В DRF есть готовый модуль, который предоставляет возможность аутентификации по токенам прямо «из коробки»: Authtoken.
Настроим этот модуль на примере Kittygram.
Фреймворк DRF в Kittygram уже подключён и настроен (установить DRF самостоятельно можно командой pip install djangorestframework
при активированном виртуальном окружении; затем добавить приложение rest_framework
в INSTALLED_APPS
).
Чтобы подключить механизм получения токена и аутентификации по токену в Django REST framework, нужно выполнить несколько действий:
- Подключить модуль authtoken: добавить строку ‘rest_framework.authtoken’ в список INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'cats.apps.CatsConfig',
]
- В settings.py в настройках REST_FRAMEWORK объявить новый способ аутентификации
TokenAuthentication
. - Запретить доступ всем неаутентифицированным пользователям: ограничение доступа настраивается с помощью пермишенов (англ. permissions, «разрешения»). Чтобы запретить доступ без токена, нужно добавить значение
IsAuthenticated
для ключаDEFAULT_PERMISSION_CLASSES
:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
]
}
- Выполнить миграции, чтобы в базе данных создались поля для работы и хранения токена:
python manage.py migrate
. - Добавить маршрут для получения токена. Подключать можно в любом urls.py, мы подключим в головном:
from rest_framework.authtoken import views
urlpatterns = [
...
path('api-token-auth/', views.obtain_auth_token),
]
Дополнительную информацию о модуле Authtoken и о его настройках можно найти в документации.
Теперь через Postman можно отправить POST-запрос к адресу api-token-auth/
, передать в теле запроса username
и password
существующего пользователя (например, суперпользователя) — и в ответ придёт заветный токен:
И что с этим делать?
Этот токен надо будет передавать в заголовке каждого запроса, в поле Authorization. Перед самим токеном должно стоять ключевое слово Token
.
При последующих запросах к API после успешной аутентификации в объекте запроса будут доступны данные о пользователе:
request.user
— экземпляр пользователя;request.auth
— токен (экземпляр классаrest_framework.authtoken.models.Token
).
Этих данных не было в запросе, но Authtoken предоставляет их, основываясь на токене и соответствующей записи в базе данных. Их можно использовать, например, для того, чтобы сопоставить аутентифицированного пользователя с пользователем, который создавал соответствующий объект в БД.
В ответ на запрос без токена клиент получит отказ, статус-код HTTP 401 Unauthorized
:
В случае указания недействительного токена (или токена с ошибкой) ответ будет таким:
{
"detail": "Invalid token."
}
Аутентификация по JWT-токену
Есть и более продвинутый способ аутентификации — по JWT-токену.
Преимущества JWT-токена в том, что прямо в нём записана информация о пользователе и сроке годности токена; системе не нужно каждый раз обращаться к базе данных, чтобы их сопоставить.Токен, созданный по стандарту JWT (JSON Web Token), состоит из трёх частей. Каждая из них записывается в формате JSON:
- header (англ. «заголовок») содержит служебную информацию;
- payload (англ. «полезная нагрузка») хранит основные данные токена;
- signature (англ. «подпись») — подпись, ключ безопасности для защиты информации.
После подготовки каждая из частей кодируется алгоритмом Base64URL. Получившиеся строки разделяются между собой точками:
Header JWT
Header, как правило, содержит два поля:
- алгоритм создания подписи — обычно применяется алгоритм HMAC-SHA256 или RSA;
- тип токена — это строка
"JWT"
.
{
"alg": "HS256",
"typ": "JWT"
}
Payload JWT
Payload хранит тип токена, timestamp со сроком его действия и информацию для аутентификации:
{
"token_type": "access",
"exp":1578171903,
"user_id":5
}
Signature JWT
Подпись гарантирует, что содержимое header
и payload
в токене не было изменено после создания. Специальный алгоритм генерирует подпись на основе содержимого header
и payload
. При кодировании этот алгоритм использует секретный ключ, который известен только серверу.
С этого момента в учебном проекте Kittygram вы будете работать именно с JWT-токенами. Для этого придётся отключить аутентификацию через Authtoken (если, конечно, вы её подключали).
Практика: подключите и настройте JWT-аутентификацию
Для работы с JWT в Django установите и подключите две библиотеки Djoser и Simple JWT:
pip install djoser djangorestframework_simplejwt
Обновите файл settings.py:
STALLED_APPS = (
'django.contrib.auth',
...
'rest_framework',
'djoser',
)
Обратите внимание, приложение Djoser должно быть зарегистрировано после django.contrib.auth
и rest_framework
.
Добавьте новые настройки в settings.py, они сходны с настройками Authtoken:
- permission;
- способ аутентификации по умолчанию;
- минимально необходимые настройки модуля Simple JWT.
from datetime import timedelta
...
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
SIMPLE_JWT = {
# Устанавливаем срок жизни токена
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'AUTH_HEADER_TYPES': ('Bearer',),
}
Проверьте, нет ли в проекте неприменённых миграций: python manage.py migrate
.
Измените файл роутинга urls.py:
urlpatterns = [
...
# Djoser создаст набор необходимых эндпоинтов.
# базовые, для управления пользователями в Django:
path('auth/', include('djoser.urls')),
# JWT-эндпоинты, для управления JWT-токенами:
path('auth/', include('djoser.urls.jwt')),
]
Готово. Но прежде чем продолжить — посмотрите на некоторые из эндпоинтов, которые стали доступны в API.
Хорошая новость заключается в том, что djoser сделал за вас большую часть тяжёлой работы: он сгенерировал эндпоинты, управляющие токенами и пользователями через API.
Список всех эндпоинтов, которые создаёт djoser, есть в документации.
Для обработки добавленных эндпоинтов djoser использует собственные вьюсеты и сериализаторы.
Если нужно изменить набор полей сериализатора из djoser, то
- из
djoser.serializers
импортируется класс сериализатора, который нужно переопределить (например,UserSerializer
илиUserCreateSerializer
; полный список сериализаторов djoser доступен в документации); - описывается новый класс сериализатора (он наследуется от импортированного);
- в новом сериализаторе переопределяется набор полей, используемых по умолчанию.
from djoser.serializers import UserSerializer
...
class CustomUserSerializer(UserSerializer):
class Meta:
model = User
fields = ('email', 'id', 'username', 'first_name', 'last_name')
Аналогично можно поступить и с вьюсетом, который использовал сериализатор.
from djoser.views import UserViewSet
...
from .serializers import CustomUserSerializer
...
class CustomUserViewSet(UserViewSet):
...
Применение JWT на практике
Теперь пользователя можно создать через API.
Придумайте новую пару «логин-пароль» и отправьте POST-запрос на http://127.0.0.1:8000/auth/users/, передав их в полях username
и password
.
Если вы всё сделали правильно, вам вернётся HTTP-ответ со статус-кодом 201 Created:
Теперь можно получить токен: отправьте POST-запрос на эндпоинт /auth/jwt/create/, передав действующий логин и пароль в полях username
и password
.
API вернёт JWT-токен:
# Пример ответа
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYyMDk0MTQ3NywianRpIjoiODUzYzE5MTg5NzMwNDQwNTk1ZjI3ZTBmOTAzZDcxZDEiLCJ1c2VyX2lkIjoxfQ.0vJBPIUZG4MjeU_Q-mhr5Gqjx7sFlO6AShlfeINK8nA",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjIwODU1Mzc3LCJqdGkiOiJkY2EwNmRiYTEzNWQ0ZjNiODdiZmQ3YzU2Y2ZjNGE0YiIsInVzZXJfaWQiOjF9.eZfkpeNVfKLzBY7U0h5gMdTwUnGP3LjRn5g8EIvWlVg"
}
Токен вернётся в поле access
, а данные из поля refresh
пригодятся для обновления токена.
Если ваш токен утрачен, украден или каким-то иным образом скомпрометирован, вам понадобится отключить его и получить новый. Для этого отправьте POST-запрос на тот же адрес /auth/jwt/create/
, а в поле refresh
передайте refresh-токен.
Этот токен также надо будет передавать в заголовке каждого запроса, в поле Authorization. Перед самим токеном должно стоять ключевое слово Bearer
и пробел.
Слово Bearer
(англ. «носитель») здесь заменяет слово Token и означает, что за ним следует сам токен.
В Postman существует возможность передавать токен и по-другому: можно выбрать соответствующий тип авторизации во вкладке Authorization и указать JWT-токен уже там.
Сохраните ссылки на документацию рассмотренных библиотек. Вы с ними обязательно встретитесь в ближайшем будущем.