Аутентификация по токенам. Пакет Djoser

Курс по Django: https://stepik.org/a/183363

Пришло время познакомиться с процессом авторизации и аутентификации пользователей посредством токенов. Базовая идея, лежащая во всех подобных алгоритмах, использование определенного ключа (токена) для идентификации пользователя. Причем токен не привязан ни к домену, ни к браузеру, главное указать его в заголовке запроса, а откуда он был отправлен – неважно. Благодаря этому сайт легко может взаимодействовать с самыми разными конечными устройствами при аутентификации пользователей.

В Django REST Framework популярны два подхода при реализации токенов:

  • стандартная аутентификация токенами (библиотека Djoser);
  • JWT-токены (библиотека Simple JWT).

но первый подход используется заметно чаще.

По идее, обычные токены в DRF можно реализовать напрямую, без установки каких-либо дополнительных библиотек. В официальной документации:

https://www.django-rest-framework.org/api-guide/authentication

подробно объясняется, как это сделать. Но, при разработке реальных проектов, очень часто применяют библиотеку Djoser, если требуется реализовать авторизацию и аутентификацию пользователей посредством обычных токенов. Эта библиотека содержит довольно богатый функционал и может быть адаптирована под каждую конкретную задачу. А потому нет смысла детально реализовывать функционал, который уже представлен в виде пакета Djoser. Хотя, я рекомендую первый раз сделать токены вручную, опираясь на документацию DRF. Пусть это будет вашим домашним заданием. Ну а мы в рамках этого занятия познакомимся с механизмом авторизации и аутентификации с помощью обычных токенов с использованием Djoser.

Идея аутентификации по токенам

Вначале разберемся с порядком авторизации и аутентификации пользователей по токенам. Предположим, пользователь зарегистрирован на некотором сайте и вводит в форму логин и пароль. Данные отправляются обратно на сервер POST-запросом и сверяются с БД. Если указанная пара логин/пароль присутствуют в таблице БД, то сервер возвращает пользователю некую комбинацию из букв и цифр, которая и является токеном. Кроме того, данный токен заносится в БД на сервере и сохраняется в любом защищенном локальном хранилище на устройстве.

В дальнейшем для аутентификации пользователя в каждом заголовке запроса от него к серверу прописывается специальная строчка, например:

Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a

Сервер читает заголовок запроса, находит запись с токеном, сверяет его по своей БД и если находит, то пользователь считается авторизованным.

Обратите внимание, время жизни токена, по умолчанию, достаточно большое (разумеется, пока пользователь не выйдет из системы и токен не будет удален из таблицы). То есть, единожды полученный токен может быть многократно использован самыми разными устройствами для доступа к приватной информации.

Реализация токенов с помощью пакета Djoser

Давайте теперь реализуем аутентификацию по токенам с помощью пакета Djoser. Подробную информацию по этому пакету можно посмотреть на официальном сайте:

https://djoser.readthedocs.io/en/latest/

Вначале нам его нужно установить. Для этого в терминале выполним команду:

pip install djoser

и начнется установка пакета со всеми необходимыми зависимостями.

Далее, подключим эту библиотеку к нашему проекту. Перейдем в drfsite/settings.py и в коллекцию INSTALLED_APPS добавим следующие строчки:

INSTALLED_APPS = [
    ...
 
    'rest_framework.authtoken',
    'djoser',
]

Первая строчка – это подключение стандартной модели таблицы для поддержки токенов, а вторая – непосредственно пакет Djoser.

Так как у нас появилась новая модель, то нужно выполнить миграции для создания таблицы непосредственно в  БД:

python manage.py migrate

Все, приложение подключено и таблицы созданы. Осталось связать Djoser с конкретными маршрутами. Откроем документацию:

https://djoser.readthedocs.io/en/latest/authentication_backends.html

Здесь указаны пути, которые нужно прописать в файле drfsite/urls.py в коллекции urlpatterns:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/women/', WomenAPIList.as_view()),
    path('api/v1/women/<int:pk>/', WomenAPIUpdate.as_view()),
    path('api/v1/womendelete/<int:pk>/', WomenAPIDestroy.as_view()),
    path('api/v1/drf-auth/', include('rest_framework.urls')),
    path('api/v1/auth/', include('djoser.urls')),          # new
    re_path(r'^auth/', include('djoser.urls.authtoken')),  # new
]

Также в файле drfsite/settings.py необходимо разрешить авторизацию по токенам:

REST_FRAMEWORK = {
    ...
 
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}

Теперь у нас все готово и мы можем протестировать работу приложения. Запустим тестовый веб-сервер:

python manage.py runserver

Первым делом попробуем создать нового пользователя. Да, библиотека Djoser позволяет добавлять, удалять, изменять данные по пользователям. Все это подробно описано в разделе «Base Endpoints» официальной документации:

https://djoser.readthedocs.io/en/latest/base_endpoints.html

Мы здесь видим, что для регистрации нового пользователя нужно обязательно по POST-запросу передать имя пользователя и пароль по адресу:

http://127.0.0.1:8000/api/v1/auth/users/

Сделаем это с помощью программы Postman, которая будет имитировать запрос от некоего устройства, например, смартфона. Вводим указанный адрес, выбираем тип запроса POST и во вкладке «Body» отмечаем пункт «form-data» и вводим:

username: seconduser
password: fghgfhhuser123
email: seconduser@mail.ru

Нажимаем кнопку «Send» и видим, что новый пользователь был успешно добавлен. Аналогично можно делать и остальные действия с пользователями, приведенные в документации.

Давайте теперь авторизуемся с помощью этого нового пользователя. Перейдем в раздел «Token Endpoints» документации:

https://djoser.readthedocs.io/en/latest/token_endpoints.html

и видим, что для авторизации нужно использовать маршрут:

http://127.0.0.1:8000/auth/token/login/

Откроем в Postman новую вкладку, введем этот URL-адрес, выберем метод POST, перейдем во вкладку «Body», отметим «form-data» и укажем для ввода:

username: seconduser
password: fghgfhhuser123

После отправки запроса нам возвращается ответ в виде JSON-строки с присвоенным токеном для последующей аутентификации:

{"auth_token":"ff8e194d179c939842a90c2d725ce5f40da0ec36"}

Давайте проверим, как работает аутентификация через токены. Для этого мы в классе WomenAPIUpdate разрешим получать доступ к отдельной записи только авторизованным пользователям:

class WomenAPIUpdate(generics.RetrieveUpdateAPIView):
    queryset = Women.objects.all()
    serializer_class = WomenSerializer
    permission_classes = (IsAuthenticated, )

Затем, перейдем в программу Postman и во вкладке укажем адрес:

http://127.0.0.1:8000/api/v1/women/9/

Если сейчас выполнить GET-запрос, то получим ответ:

{"detail":"Учетные данные не были предоставлены."}

Чтобы сервер принял нас, как авторизованного пользователя, в заголовке запроса нужно прописать строку:

Authorization: Token ff8e194d179c939842a90c2d725ce5f40da0ec36

Теперь, при отправке запроса мы получаем данные по указанной записи. То есть, указав в GET-запросе выданный нам токен, мы успешно проходим процедуру аутентификации на сервере и получаем доступ к закрытой информации.

Наконец, чтобы выйти из системы, нам нужно отправить POST-запрос на адрес:

http://127.0.0.1:8000/auth/token/logout/

Для этого в Postman на новой вкладке укажем этот URL, выберем метод POST и если сейчас отправить его, то вернется строка, что пользователь не авторизован. Поэтому здесь нам также нужно в заголовке запроса указать:

Authorization: Token ff8e194d179c939842a90c2d725ce5f40da0ec36

Теперь, при повторной отправке мы видим значение 1, то есть, токен был успешно удален из БД и теперь он недействителен. Это легко можно проверить, если отправить запрос на адрес:

http://127.0.0.1:8000/api/v1/women/9/

Получим ответ от сервера:

{"detail":"Недопустимый токен."}

то есть, мы действительно вышли из системы.

Сейчас в нашем проекте пользователи могут использовать две независимые системы авторизации: через сессии и токены. Однако, на уровне каждого отдельного класса представления мы можем конкретизировать способ аутентификации пользователя. Например, если в классе WomenAPIUpdate прописать атрибут authentication_classes с классом TokenAuthentication, то данные по записи можно получать только при авторизации по токенам:

class WomenAPIUpdate(generics.RetrieveUpdateAPIView):
    queryset = Women.objects.all()
    serializer_class = WomenSerializer
    authentication_classes = (TokenAuthentication, )
    permission_classes = (IsAuthenticated, )

Перейдем в браузере на страницу:

http://127.0.0.1:8000/api/v1/women/9/

и видим, что данные не отображаются. Даже если мы авторизуемся через механизм сессий, то все равно данные не будут отображены. А вот через токены мы имеем к ним доступ. Вот так тонко на уровне отдельных представлений можно управлять способом атуентификации пользователей.

Я, думаю, из этого занятия вы поняли основной принцип реализации и взаимодействия с учетными записями пользователей через токены с использованием пакета Djoser. На следующем занятии мы продолжим эту тему и поговорим о JWT-токенах.

Курс по Django: https://stepik.org/a/183363