Evgenii Legotckoi
Evgenii Legotckoi14 лютого 2022 р. 04: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 р. 06:42

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

Evgenii Legotckoi
  • 14 лютого 2022 р. 06:54
  • (відредаговано)

Добрый день

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

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

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

BL4CK R4BBIT
  • 14 лютого 2022 р. 07: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 р. 07:22

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
Г

C++ - Тест 001. Первая программа и типы данных

  • Результат:66бали,
  • Рейтинг балів-1
t

C++ - Тест 001. Первая программа и типы данных

  • Результат:33бали,
  • Рейтинг балів-10
t

Qt - Тест 001. Сигналы и слоты

  • Результат:52бали,
  • Рейтинг балів-4
Останні коментарі
G
GoattRock03 вересня 2024 р. 13:50
Як скопіювати файли в Linux Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
ВР
Влад Русоков02 серпня 2024 р. 01:47
Як скопіювати файли в Linux Screenshot_20240802-065123.png
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr08 лютого 2024 р. 18:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 01:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
Fynjy22 липня 2024 р. 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCT25 червня 2024 р. 01:00
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCT05 травня 2024 р. 05:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii Legotckoi02 травня 2024 р. 14:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Слідкуйте за нами в соціальних мережах