Evgenii Legotckoi
Evgenii Legotckoi14 февраля 2022 г. 4:24

Django Rest Framework - Урок 001. Добавление аутентификации по токену

На данный момент я активно работаю над приложение, которое будет работат с REST API сайта на Django. И одним из первых шагов была настройка аутентификации пользователя по токену, но для того, чтобы это заработало, нужно сначала получить токен авторизации.

Давайте рассмотрим, как это можно сделать.


Установка Django Rest Framework

Поскольку это самая первая статья по применению DRF, то опишу более подробный процесс установки и настройки DRF. В Дальнейшем я уже не буду приводить эти инструкции.

Устанавливаем DRF и остальный необходимые пакеты

pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter

Далее добавляем приложение 'rest_frameword' в INSTALLED_APPS в файле конфигурации сайта settings.py

INSTALLED_APPS = [
    ...
    'rest_framework',
]

Далее официальная документация предлагает подключить в urls.py файле login и logout View из rest_framework, чтобы можно было удобне пользоваться API в браузере.

Если честно, то я не использую это API, поскольку мне удобнее пользоваться документацией, сгенерированной через swagger, о чём я тоже скажу далее, но я всё равно подключаю данные View, чтобы потом не решать случайные проблемы.

urlpatterns = [
    ...
    path('api-auth/', include('rest_framework.urls')),
]

Далее настраиваем классы аутентификации и прав доступа по умолчанию (также в settingsp.py)

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

Теперь всё готово для того, чтобы написать точку доступа для получения токена. Но предвартельно я хочу подключить ещё и генерирование swagger документации. Просто так удобнее работать, мне например очень нравится документация redoc, в которой довольно красиво и удобно всё отображается.

Установка Swagger

А теперь сразу подключим swagger на сайте. Я понимаю, что это не относится к теме статьи, но в коде я делаю специальные манипуляции, чтобы подключить специальное отображение документации и считаю, что в дальнейшем это может Вам пригодится, покоскольку:

  • Вы будете в дальнейшем использовать swagger
  • Вам пондобится иногда кастомизировать вывод информации в описании API, а именно для токена у меня была самое специфичное изменение для swagger.

Установим drf-yasg

Для этого руководствуясь документацией выполним следующую команду

pip install -U drf-yasg

После чего модифицируем файл settings.py

INSTALLED_APPS = [
   ...
   'django.contrib.staticfiles',  # required for serving swagger ui's css/js files
   'drf_yasg',
   ...
]

А теперь зарегистрируем swagger в urls сайта

from django.urls import re_path
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions

schema_view = get_schema_view(
   openapi.Info(
      title="EXAMPLE API",
      default_version='v1',
      description="REST API Documentation",
      contact=openapi.Contact(email="example@example.com"),
   ),
   public=True,
   permission_classes=(permissions.IsAdminUser,),
)

urlpatterns = [
    ...
    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
    re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]

Если вы внимательно посмотрите на код, то увидите, что я зарегистрировал swagger только для администрации сайта. Если хотите, чтобы документация была открытой, то удалите эту строку

permission_classes=(permissions.IsAdminUser,),

Дополнительно можно добавить информацию в вывод документации о том, какие типы аутентификации используются для API. Это выполняется в файле settings.py.

SWAGGER_SETTINGS = {
   'SECURITY_DEFINITIONS': {
      'Basic': {
            'type': 'basic'
      },
      'Bearer': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'header'
      }
   }
}

Вот теперь можно приступить к реализации точку доступа для получения токена.

Приложение api

Создадим приложение api, в котором будет добавлена конечная точка для получения токена.

python manage.py startapp api

serializers.py

Далее создадим файл с сериализаторами, в котором будет создан специальный сериализатор, который будет описывать ответ сайта в swagger.

# -*- coding: utf-8 -*-

from rest_framework import serializers


class Response201AuthTokenSerializer(serializers.Serializer):
    token = serializers.CharField(required=True, allow_blank=False)
    user_id = serializers.IntegerField(required=True)

Этот сериализатор будет корректно генерировать информацию об ответе в документации, и я его использую для создания JSON ответа с токеном.

views.py

После чего модифицирем файл views.py, чтобы получить конечную точку.

# -*- coding: utf-8 -*-

from django.contrib.auth import get_user_model
from django.http import JsonResponse
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import authentication, viewsets
from rest_framework.authtoken.views import ObtainAuthToken

from api.serializers import Response201AuthTokenSerializer


class CustomAuthToken(ObtainAuthToken):
    authentication_classes = [authentication.BasicAuthentication]

    @swagger_auto_schema(responses={
        "201": openapi.Response(
            description=_("User has got Token"),
            schema=Response201AuthTokenSerializer,
        )
    })
    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        response201 = Response201AuthTokenSerializer(data={"token": token.key, "user_id": user.pk})
        response201.is_valid(raise_exception=True)
        return JsonResponse(response201.data)

Как видите в коде имеется кастомизированная схема swagger для ответа 201, контент создан. Из сериализатора Response201AuthTokenSerializer будет сформирована корректная документация.
Помимо прочего я использую Response201AuthTokenSerializer, чтобы сфомировать ответ с токеном, а также пользовательским id. В дальнейшем я использую id пользователя для манипуляции с данными в мобильном приложении.
Отличительным нюансом данного view является то, что в качестве аутентификации используется только BasicAuthenttication, то есть пользователь получает доступ к данном view только через username и password. В дальнейшем разработка API ведётся для использования токена.

urls.py

А теперь подключить view для получения токена в файлах urls

# -*- coding: utf-8 -*-

from django.urls import path

from api import views


app_name = 'api'
urlpatterns = [
    path('api-token-auth/', views.CustomAuthToken.as_view()),
]

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

И в конце подключаем urls.py приложения api в основном файле urls.py всего проекта.

urlpatterns = [
    ...
    path('api/', include('api.urls')),
    ...
]

Теперь, чтобы получить токен, необходимо направить POST запрос по url http://127.0.0.1/api/api-token-auth/ , в том случае, чтобы тестировать функционал на локальном сервере. В случае боевого сервера нужно отправлять запрос по адресу с доменом вашего сайта.

Для аутентификации нужно использовать Basic аутентификацию.

Пример запроса токена в Felgo QML

Вот пример кода для запроса токена в приложении написанном на QML Felgo

function login(username, password, success, error) {
    HttpRequest
    .post("http://127.0.0.1/api/api-token-auth/", {username: username, password: password})
    .timeout(5000)
    .set('Content-Type', 'application/json')
    .then(function(res) { success(res) })
    .catch(function(err) { error(err) });
}
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

BL4CK R4BBIT
  • 14 февраля 2022 г. 6:42

Доброго времени.
А если кастомный юзер.
И активация юзера подтверждением по смс или звонку?

Evgenii Legotckoi
  • 14 февраля 2022 г. 6:54
  • (ред.)

Добрый день

Если Вы имеетe ввиду, что модель пользователя наследована от AbstractUser и переопределена для проекта, то да - этот пример рабочий, я сейчас занимаюсь одним проектом, который будет иметь приложение и использую там кастомного пользователя.

А что касается подтверждения по СМС, то я участвовал несколько лет назад в проекте, где было подтверждение по СМС. Код показать не могу, но суть была в том, что там было два REST API View, сначало приложение стучалось на первый url, куда посылало логин и пароль, при получении ответа 200, приложение меняло форму ввода данных и ждало, пока пользователь заполнит код из СМС и подтвердит отправку. В этом случае отправка кода была уже на второй url, который проверял правильность кода, и уже в этом случае отдавал токен.

Естественно первый URL, который обрабатывал логин и пароль, стучался на API стороннего провайдера по отправке СМС, получал отправленный код и сохранял в отдельную таблицу, чтобы потом сравнить код и соответствие пользователя коду.

BL4CK R4BBIT
  • 14 февраля 2022 г. 7:14
  • (ред.)

В моем случае кастом от AbstractBaseUser

class User(AbstractBaseUser):
    phone       = PhoneNumberField(region='RU', unique=True)
    username    = models.CharField(max_length = 150, blank = True, null = True)
    standard    = models.CharField(max_length = 3, blank = True, null = True)
    score       = models.IntegerField(default = 16)
    first_login = models.BooleanField(default=False)
    active      = models.BooleanField(default=False)
    staff       = models.BooleanField(default=False)
    admin       = models.BooleanField(default=False)
    timestamp   = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = 'phone'
    REQUIRED_FIELDS = []

    objects = UserManager()

    def __str__(self):
        return self.phone

    def get_full_name(self):
        return self.phone

    def get_short_name(self):
        return self.phone

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):

        return True

    @property
    def is_staff(self):
        return self.staff

    @property
    def is_admin(self):
        return self.admin

    @property
    def is_active(self):
        return self.active

Только токен я использую knox(Стандарт мобильной безопастности Samsung), потому и спросил

Дальше понятно:
Регистрация post на страницу урл api/register(можно сразу отправить запрос на звонок, после создания пользователя)
Если удачно то получим 4-x символьный код от провайдера, запищем его. Если неудачно то создаем свой и шлем смс
Cледом post-запрос на верификацию с кодом(при удаче - активируем пользователя, при не удаче 4 попытки на повтор)

Evgenii Legotckoi
  • 14 февраля 2022 г. 7:22

В таком случае без напильника этот код сходу не взлетит, если конкретно knox нужно использовать.
А касательно самого AbstractBaseUser, то должно заработать.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 7:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 7:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 17:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 19:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 14:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 13:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 17:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
m
moogo22 ноября 2024 г. 13:17
Mosquito Spray System Effective Mosquito Systems for Backyard | Eco-Friendly Misting Control Device & Repellent Spray - Moogo ; Upgrade your backyard with our mosquito-repellent device! Our misters conce…
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 21:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 12:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 9:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…

Следите за нами в социальных сетях